SizeOf是一个用于计算JVM堆中对象所占用内存的小工具,使用起来很方便。今天没事把源码down下来看了看,同时写了个笔记贴上来,希望对看到的人有所帮助。
-----------------------------------
SizeOf主要思路是通过jdk1.5提供的新特性Instrumentation计算对象占用内存大小。
jdk文档中java.lang.instrument.Instrumentation介绍:
此类提供检测 Java 编程语言代码所需的服务。检测是向方法中添加字节码,以搜集各种工具所使用的数据。由于更改完全是进行添加,所以这些工具不修改应用程序的状态或行为。这种无害工具的例子包括镜像代理、分析器、覆盖分析器和事件记录器。
源码分析:
项目主页:http://sourceforge.net/projects/sizeof/
整个源码只有一个类SizeOf.java,重点是sizeOf和deepSizeOf两个方法。
1、premain:
通过premain注入VM中Instrumentation实例。
private static Instrumentation inst;
public static void premain(String options, Instrumentation inst)
{
SizeOf.inst = inst;
System.out.println("JAVAGENT: call premain instrumentation for class SizeOf");
}
JVM以代理类方式启动时,回调premain方法,并注入Instrumentation实例。
2、sizeOf:
计算一个对象的大小,但该对象内引用的其它对象的大小不计算在内。
首先,SizeOf类内有3个与计算对象大小相关的开关:
private static boolean SKIP_STATIC_FIELD = false;//是否计算static属性
private static boolean SKIP_FINAL_FIELD = false;//是否计算final属性
private static boolean SKIP_FLYWEIGHT_FIELD = false;//是否计算共享(享元)对象
sizeOf方法:
public static long sizeOf(Object object)
{
if (inst == null)
throw new IllegalStateException("Instrumentation is null");
//是否计算共享对象
if (SKIP_FLYWEIGHT_FIELD && isSharedFlyweight(object))
return 0;
return inst.getObjectSize(object);
}
该方法内部直接通过Instrumentation的getObjectSize方法计算当前传入对象占用内存的大小,需要注意的是,这里有一个isSharedFlyweight方法:
private static boolean isSharedFlyweight(Object obj)
{
// optimization - all of our flyweights are Comparable
if (obj instanceof Comparable)
{
if (obj instanceof Enum)
{
return true;
} else if (obj instanceof String)
{
return (obj == ((String) obj).intern());
} else if (obj instanceof Boolean)
{
return (obj == Boolean.TRUE || obj == Boolean.FALSE);
} else if (obj instanceof Integer)
{
return (obj == Integer.valueOf((Integer) obj));
} else if (obj instanceof Short)
{
return (obj == Short.valueOf((Short) obj));
} else if (obj instanceof Byte)
{
return (obj == Byte.valueOf((Byte) obj));
} else if (obj instanceof Long)
{
return (obj == Long.valueOf((Long) obj));
} else if (obj instanceof Character)
{
return (obj == Character.valueOf((Character) obj));
}
}
return false;
}
该方法的作用是判断传入对象是否为享元对象,可以理解为共享一块内存区域的对象,这部分内存可以不进行计算统计(因为每个对象都共同使用一块内存)。所有享元对象都实现了Comparable接口。
(1)关于String.intern():String类内部维护着一个字符串池,如果池中包含一个等于此字符串的对象,则返回池中对象,否则将此String对象添加到池中,并返回此String对象的引用。intern()方法会从字符串池中取出与这个字符串内容相同的字符串对象,也就是说String.intern()返回的是空享内存空间的对象。
(2)关于基本数据类型:
至于Boolean、Integer、Short、Byte...等基本数据类型包装的对象怎么也会被算作共享内存区域呢?查了一些资料,原因是这样的:就拿Integer类来说,它会缓存了-128到127这256个状态的Integer值,如果使用Integer.valueOf(int i),传入的int范围正好在此内,就返回静态实例。
也就是说,如果你使用了Integer.valueOf()时,有可能直接返回静态实例。
可见在日常编程中,能用Boolean.TRUE或FALSE就不要用new Boolean(),能用Integer.valueOf的时候就不要用new Integer(),其它各数据类型也都如此。
如果传入对象内部属性有引用其它对象时,sizeOf方法不计算引用对象的大小(详见deepSizeOf)。
3、deepSizeOf方法:
public static long deepSizeOf(Object objectToSize)
{
//Set<Integer> doneObj = new HashSet<Integer>();
Map<Object,Object> doneObj = new IdentityHashMap<Object,Object>();
return deepSizeOf(objectToSize, doneObj, 0);
}
首先声明了一个IdentityHashMap,jdk文档中关于IdentityHashMap的解释:
此类利用哈希表实现Map接口,比较键(和值)时使用引用相等性代替对象相等性。换句话说,在 IdentityHashMap中,当且仅当(k1==k2)时,才认为两个键k1和k2相等。
然后是deepSizeOf的重载方法,这个方法有点长,分段来解析。
private static long deepSizeOf(Object o, Map<Object,Object> doneObj, int depth)
{
if (o == null)
{
if (debug)
print("null\n");
return 0;
}
long size = 0;
if (doneObj.containsKey(o))
{
if (debug)
print("\n%s{ yet computed }\n", indent(depth));
return 0;
}
if (debug)
print("\n%s{ %s\n", indent(depth), o.getClass().getName());
doneObj.put(o,null);//从引用map中获取该属性
size = sizeOf(o);//计算obj的大小
//...
如果传入对象的引用在IdentityHashMap中已经包含,说明已经计算过该对象,则返回0,否则直接将该引用放入IdentityHashMap之后直接调用sizeOf方法计算对象大小。
if (o instanceof Object[])//对象为数组
{
int i = 0;
for (Object obj : (Object[]) o)
{
if (debug)
print("%s [%d] = ", indent(depth), i++);
//取数组中的每个元素,递归调用
size += deepSizeOf(obj, doneObj, depth + 1);
}
} else
如果对象为数组,则取数组的每个元素递归调用deepSizeOf方法,计算每个对象大小。
else //该对象不为数组
{
//获取每个属性
Field[] fields = o.getClass().getDeclaredFields();
//遍历每个属性
for (Field field : fields)
{
//值为true则指示反射的对象在使用时应该取消Java语言访问检查
field.setAccessible(true);
Object obj;
try
{
obj = field.get(o);//取出该属性在对象上的值
} catch (IllegalArgumentException e)
{
throw new RuntimeException(e);
} catch (IllegalAccessException e)
{
throw new RuntimeException(e);
}
//判断该属性是否可计算
if (isComputable(field))
{
if (debug)
print("%s %s = ", indent(depth), field.getName());
size += deepSizeOf(obj, doneObj, depth + 1);//递归调用计算各属性对象大小
} else
{
if (debug)
print("%s %s = %s\n", indent(depth), field.getName(), obj.toString());
}
}
}
如果传入对象不为数组,则遍历每个属性,取出每个属性在对象上的值,然后判断该属性是否可被计算,如果可以,则递归调用deepSizeOf方法,累加size大小。
这里有一个方法isComputable如下:
private static boolean isComputable(Field field)
{
int modificatori = field.getModifiers();
if (isAPrimitiveType(field.getType()))
return false;
else if (SKIP_STATIC_FIELD && Modifier.isStatic(modificatori))
return false;
else if (SKIP_FINAL_FIELD && Modifier.isFinal(modificatori))
return false;
else
return true;
}
对3种类型的属性进行判断:原始类型的包装类型、static类型和final类型。其中后两种可有开关控制,第一种由于在sizeOf方法中的getObjectSize计算过了,此处不需要重复计算。
至此,deepSizeOf部分的代码也分析完了。
结束语:
今天对SizeOf的源码分析就先告一段落,希望对看到的人有所帮助。
关于开源工具SizeOf,这里还有我的另一篇Blog,欢迎多提些建议。
使用SizeOf测定JVM中对象占用内存: http://shensy.iteye.com/blog/1765760