原文:http://yueyemaitian.iteye.com/blog/2033046
老早之前寫過一篇博客,是關於一個Integer對象到底占用多少字節的,現在看來,那篇文章竟然計算錯了。這次再去計算,是因為之前寫的一篇關於字長的文章里,看到了hotspot jvm里,對象占用空間是8字節對齊的,再加上之前關於字節那文章里帶着一點-XX:+UseCompressedOops壓縮指針參數的疑問,重新探究了下一個對象到底占用多少字節,以及如何計算它占用空間的方法。主要是參考了這篇很久以前的文章,不過試驗了一把,instrumentation這種方法還是靠譜的。
1 import java.lang.instrument.Instrumentation; 2 import java.lang.reflect.Array; 3 import java.lang.reflect.Field; 4 import java.lang.reflect.Modifier; 5 import java.util.ArrayDeque; 6 import java.util.Deque; 7 import java.util.HashSet; 8 import java.util.Set; 9 10 /** 11 * 對象占用字節大小工具類 12 * 13 * @author tianmai.fh 14 * @date 2014-03-18 11:29 15 */ 16 public class SizeOfObject { 17 static Instrumentation inst; 18 19 public static void premain(String args, Instrumentation instP) { 20 inst = instP; 21 } 22 23 /** 24 * 直接計算當前對象占用空間大小,包括當前類及超類的基本類型實例字段大小、<br></br> 25 * 引用類型實例字段引用大小、實例基本類型數組總占用空間、實例引用類型數組引用本身占用空間大小;<br></br> 26 * 但是不包括超類繼承下來的和當前類聲明的實例引用字段的對象本身的大小、實例引用數組引用的對象本身的大小 <br></br> 27 * 28 * @param obj 29 * @return 30 */ 31 public static long sizeOf(Object obj) { 32 return inst.getObjectSize(obj); 33 } 34 35 /** 36 * 遞歸計算當前對象占用空間總大小,包括當前類和超類的實例字段大小以及實例字段引用對象大小 37 * 38 * @param objP 39 * @return 40 * @throws IllegalAccessException 41 */ 42 public static long fullSizeOf(Object objP) throws IllegalAccessException { 43 Set<Object> visited = new HashSet<Object>(); 44 Deque<Object> toBeQueue = new ArrayDeque<>(); 45 toBeQueue.add(objP); 46 long size = 0L; 47 while (toBeQueue.size() > 0) { 48 Object obj = toBeQueue.poll(); 49 //sizeOf的時候已經計基本類型和引用的長度,包括數組 50 size += skipObject(visited, obj) ? 0L : sizeOf(obj); 51 Class<?> tmpObjClass = obj.getClass(); 52 if (tmpObjClass.isArray()) { 53 //[I , [F 基本類型名字長度是2 54 if (tmpObjClass.getName().length() > 2) { 55 for (int i = 0, len = Array.getLength(obj); i < len; i++) { 56 Object tmp = Array.get(obj, i); 57 if (tmp != null) { 58 //非基本類型需要深度遍歷其對象 59 toBeQueue.add(Array.get(obj, i)); 60 } 61 } 62 } 63 } else { 64 while (tmpObjClass != null) { 65 Field[] fields = tmpObjClass.getDeclaredFields(); 66 for (Field field : fields) { 67 if (Modifier.isStatic(field.getModifiers()) //靜態不計 68 || field.getType().isPrimitive()) { //基本類型不重復計 69 continue; 70 } 71 72 field.setAccessible(true); 73 Object fieldValue = field.get(obj); 74 if (fieldValue == null) { 75 continue; 76 } 77 toBeQueue.add(fieldValue); 78 } 79 tmpObjClass = tmpObjClass.getSuperclass(); 80 } 81 } 82 } 83 return size; 84 } 85 86 /** 87 * String.intern的對象不計;計算過的不計,也避免死循環 88 * 89 * @param visited 90 * @param obj 91 * @return 92 */ 93 static boolean skipObject(Set<Object> visited, Object obj) { 94 if (obj instanceof String && obj == ((String) obj).intern()) { 95 return true; 96 } 97 return visited.contains(obj); 98 } 99 }
跑代碼前,需要按照那篇很老的文章先打包,這樣才能注入Instrumentation實例,打包時候需要在MANIFEST.MF中寫入三項值(注意包路徑名改成自己的包名):
1 Premain-class: xxx.yyy.zzz.SizeOfObject 2 Can-Redefine-Classes: false 3 Boot-Class-Path:
來看看測試類:
1 import java.io.File; 2 import static com.tmall.buy.structure.SizeOfObject.*; 3 /** 4 * @author tianmai.fh 5 * @date 2014-03-18 20:17 6 */ 7 public class SizeOfObjectTest { 8 /** 9 * -XX:+UseCompressedOops: mark/4 + metedata/8 + 4 = 16 10 * -XX:-UseCompressedOops: mark/8 + metedata/8 + 4 + padding/4 = 24 11 */ 12 static class A { 13 int a; 14 } 15 16 /** 17 * -XX:+UseCompressedOops: mark/4 + metedata/8 + 4 + 4 + padding/4 = 24 18 * -XX:-UseCompressedOops: mark/8 + metedata/8 + 4 + 4 = 24 19 */ 20 static class B { 21 int a; 22 int b; 23 } 24 25 /** 26 * -XX:+UseCompressedOops: mark/4 + metedata/8 + 4 + 4 + padding/4 = 24 27 * -XX:-UseCompressedOops: mark/8 + metedata/8 + 8 + 4 + padding/4 = 32 28 */ 29 static class B2 { 30 int b2a; 31 Integer b2b; 32 } 33 34 /** 35 * 不考慮對象頭: 36 * 4 + 4 + 4 * 3 + 3 * sizeOf(B) 37 */ 38 static class C extends A { 39 int ba; 40 B[] as = new B[3]; 41 42 C() { 43 for (int i = 0; i < as.length; i++) { 44 as[i] = new B(); 45 } 46 } 47 } 48 49 static class D extends B { 50 int da; 51 Integer[] di = new Integer[3]; 52 } 53 54 /** 55 * 會算上A的實例字段 56 */ 57 static class E extends A { 58 int ea; 59 int eb; 60 } 61 62 public static void main(String[] args) throws IllegalAccessException { 63 System.out.println(new File("./target/classes").getAbsolutePath()); 64 System.out.println("sizeOf(new Object())=" + sizeOf(new Object())); 65 System.out.println("sizeOf(new A())=" + sizeOf(new A())); 66 System.out.println("sizeOf(new B())=" + sizeOf(new B())); 67 System.out.println("sizeOf(new B2())=" + sizeOf(new B2())); 68 System.out.println("sizeOf(new B[3])=" + sizeOf(new B[3])); 69 System.out.println("sizeOf(new C())=" + sizeOf(new C())); 70 System.out.println("fullSizeOf(new C())=" + fullSizeOf(new C())); 71 System.out.println("sizeOf(new D())=" + sizeOf(new D())); 72 System.out.println("fullSizeOf(new D())=" + fullSizeOf(new D())); 73 System.out.println("sizeOf(new int[3])=" + sizeOf(new int[3])); 74 System.out.println("sizeOf(new Integer(1)=" + sizeOf(new Integer(1))); 75 System.out.println("sizeOf(new Integer[0])=" + sizeOf(new Integer[0])); 76 System.out.println("sizeOf(new Integer[1])=" + sizeOf(new Integer[1])); 77 System.out.println("sizeOf(new Integer[2])=" + sizeOf(new Integer[2])); 78 System.out.println("sizeOf(new Integer[3])=" + sizeOf(new Integer[3])); 79 System.out.println("sizeOf(new Integer[4])=" + sizeOf(new Integer[4])); 80 System.out.println("sizeOf(new A[3])=" + sizeOf(new A[3])); 81 System.out.println("sizeOf(new E())=" + sizeOf(new E())); 82 } 83 }
如果你是用maven打包的話,可以考慮在pom.xml文件中配置。打完jar包后,可以直接運行SizeOfObject了,但是要加上vm啟動參數(test.jar是剛才打的jar包):
1 -javaagent:target/test.jar
在我64bit mac上,跑64位hotspot vm的結果如下,其中壓縮對象指針參數是開啟的,即-XX:+UseCompressedOops
1 sizeOf(new Object())=16 2 sizeOf(new A())=16 3 sizeOf(new B())=24 4 sizeOf(new B2())=24 5 sizeOf(new B[3])=32 6 sizeOf(new C())=24 7 fullSizeOf(new C())=128 8 sizeOf(new D())=32 9 fullSizeOf(new D())=64 10 sizeOf(new int[3])=32 11 sizeOf(new Integer(1)=16 12 sizeOf(new Integer[0])=16 13 sizeOf(new Integer[1])=24 14 sizeOf(new Integer[2])=24 15 sizeOf(new Integer[3])=32 16 sizeOf(new Integer[4])=32 17 sizeOf(new A[3])=32 18 sizeOf(new E())=24
如果關閉指針壓縮,即在vm啟動參數中加上-XX:-UseCompressedOops結果會不一樣:
1 sizeOf(new Object())=16 2 sizeOf(new A())=24 3 sizeOf(new B())=24 4 sizeOf(new B2())=32 5 sizeOf(new B[3])=48 6 sizeOf(new C())=40 7 fullSizeOf(new C())=160 8 sizeOf(new D())=40 9 fullSizeOf(new D())=88 10 sizeOf(new int[3])=40 11 sizeOf(new Integer(1)=24 12 sizeOf(new Integer[0])=24 13 sizeOf(new Integer[1])=32 14 sizeOf(new Integer[2])=40 15 sizeOf(new Integer[3])=48 16 sizeOf(new Integer[4])=56 17 sizeOf(new A[3])=48 18 sizeOf(new E())=32
UseCompressOops開啟和關閉,對對象頭大小是有影響的,開啟壓縮,對象頭是4+8=12byte;關閉壓縮,對象頭是8+8=16bytes。這個如何觀察驗證呢?
基於上述事實,通過new A()和new B()占用字節推斷,基本類型int在開啟、關閉壓縮情況下都是占用4個bytes的,這個沒有影響。而通過B和B2在開啟、關閉指針壓縮情況下的對比看,Integer類型分別占了4 bytes和8 bytes,實際上引用類型都是這樣。如何驗證?
new Integer[0]在壓縮前后分別占用16、24個字節,這是又是為什么呢?
欲知后事,且聽下回分解!enjoy it !
