由于在项目中需要大致计算一下对象的内存占用率(Hadoop中的Reduce端内存占用居高不下却又无法解释),因此深入学习了一下如何准确计算对象的大小。
?
参考了几个大牛们的blog,例如:
http://www.cnblogs.com/yangjiandan/p/3534781.html
http://happyqing.iteye.com/blog/2013639
http://article.yeeyan.org/view/104091/62930
?
有些时候还是需要自己动手来观察一下所有的对象在内存中的状态。个人感觉GC方式并不是太可靠(手动调用System.gc(),通过Runtime.totalMemory/freeMemory的减法计算),还是考虑使用Instrument的方式,但是Instrument方式仅返回某个对象的大小而不包括其成员变量所引用的对象。
?
Instrumentation.getObjectSize()会计算的对象中的基本类型,以及引用的长度,包括数组,但不会计算其中包含的对象类型里面的对象类型内容(会计算其中内部的基本类型)。
?
这种方式要求创建一个带有public static void premain(String[] args, Instrumentation inst)方法,不能直接在IDE中直接调用该方法,只能通过构建jar包的方式,MANIFEST.MF中加入这一行:
class="java">Premain-Class: com.clamaa.serialization.test.SizeOfObject
?
使用下面的方式进行调用:
java -javaagent:*.jar <main class>?
JVM在调用时注入的执行类时,会调用到premain方法,传入Instrumentation对象,这时就可以使用Instrumentation.getObjectSize(Object)来计算对象占用的内存大小。
?
我这里直接使用了maven的方式进行调用,在maven构建时指定加入对应的MANIFEST.MF模版文件。
<plugin> <artifactId>maven-assembly-plugin</artifactId> <version>2.4.1</version> <configuration> <appendAssemblyId>true</appendAssemblyId> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifestFile>src/main/java/MANIFEST.MF</manifestFile> </archive> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin>
?
在本机运行时,由于是64位JVM,对比一下使用参数:XX+(-)UseCompressedOops。
?
?在启用指针压缩后,测试的一些数据结果:
?
Bytes used by object: 16 Bytes used by int 2120121: 16 Bytes used by Integer 202323 : 16 Bytes used by new byte[3] : 24 Bytes used by new byte[30] : 48 Bytes used by string a : 24 Bytes used by string aaaabcsdsd : 24 Bytes used by new Object[100] : 416 Bytes used by new HashMap(100) : 48 Bytes used by new HashMap(1000) : 48?
?
可以看到对应String,HashMap对象,无论其内容多大,用Instrument计算出来的只是对象的大小,但是数组不同,随着数组的大小增大,其内存占用率提高很大。
?
对象的大小如何计算?这篇blog讲的非常好:
http://www.cnblogs.com/magialmoon/p/3757767.html
??
原生类型(primitive type)的内存占用如下:
Primitive Type Memory Required(bytes) boolean ? ? ? ? ? ? ? ? ? ? ? 1 byte ? ? ? ? ? ? ? ? ? ? ? ? ? ? 1 short ? ? ? ? ? ? ? ? ? ? ? ? ?? 2 char ? ? ? ? ? ? ? ? ? ? ? ? ? ? 2 int ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? 4 float ? ? ? ? ? ? ? ? ? ? ? ? ? ? 4 long ? ? ? ? ? ? ? ? ? ? ? ? ? ? 8 double ? ? 8reference类型在32位系统上每个占用4bytes, 在64位系统上每个占用8bytes。
?
在Hotspot中对象数据的计算方式:
?
?
(对象头 + 实例数据 + padding) % 8等于0且0 <= padding < 8
?
这里就不再赘述如何计算单个对象引用的方式,可以查看上面介绍的blog。? 上述的blog中提到了一个工具:?
import java.lang.instrument.Instrumentation; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.Set; /** * 对象占用字节大小工具类 * * @author tianmai.fh * @date 2014-03-18 11:29 */ public class SizeOfObject { static Instrumentation inst; public static void premain(String args, Instrumentation instP) { inst = instP; } /** * 直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、<br></br> * 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小;<br></br> * 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小 <br></br> * * @param obj * @return */ public static long sizeOf(Object obj) { return inst.getObjectSize(obj); } /** * 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小 * * @param objP * @return * @throws IllegalAccessException */ public static long fullSizeOf(Object objP) throws IllegalAccessException { Set<Object> visited = new HashSet<Object>(); Deque<Object> toBeQueue = new ArrayDeque<Object>(); toBeQueue.add(objP); long size = 0L; while (toBeQueue.size() > 0) { Object obj = toBeQueue.poll(); //sizeOf的时候已经计基本类型和引用的长度,包括数组 size += skipObject(visited, obj) ? 0L : sizeOf(obj); Class<?> tmpObjClass = obj.getClass(); if (tmpObjClass.isArray()) { //[I , [F 基本类型名字长度是2 if (tmpObjClass.getName().length() > 2) { for (int i = 0, len = Array.getLength(obj); i < len; i++) { Object tmp = Array.get(obj, i); if (tmp != null) { //非基本类型需要深度遍历其对象 toBeQueue.add(Array.get(obj, i)); } } } } else { while (tmpObjClass != null) { Field[] fields = tmpObjClass.getDeclaredFields(); for (Field field : fields) { if (Modifier.isStatic(field.getModifiers()) //静态不计 || field.getType().isPrimitive()) { //基本类型不重复计 continue; } field.setAccessible(true); Object fieldValue = field.get(obj); if (fieldValue == null) { continue; } toBeQueue.add(fieldValue); } tmpObjClass = tmpObjClass.getSuperclass(); } } } return size; } /** * String.intern的对象不计;计算过的不计,也避免死循环 * * @param visited * @param obj * @return */ static boolean skipObject(Set<Object> visited, Object obj) { if (obj instanceof String && obj == ((String) obj).intern()) { return true; } return visited.contains(obj); } }
?
这个工具就是用于解决刚才说明的Instrumentation.getObjectSize()只能够计算对象的大小的问题。SizeOfObject中提供了两个方法,sizeOf仍然是直接计算对象大小,而fullSizeOf提供了一个用于递归计算当前对象占用大小。?递归计算当前对象占用大小时,大致根据下面的算法计算:?