如何准確計算Java對象的大小
原創文章,轉載請注明:博客園aprogramer
原文鏈接:如何准確計算Java對象的大小
有時,我們需要知道Java對象到底占用多少內存,有人通過連續調用兩次System.gc()比較兩次gc前后內存的使用量在計算java對象的大小,也有人根據Java虛擬機規范中的Java對象內存排列估算對象的大小,這兩種方法或多或少都有問題,因為System.gc()並不一定促發GC,同一個類型的對象在32位與64位JVM中使用的內存會不一樣,在64位虛擬機中是否開啟指針壓縮也會影響Java對象在內存中的大小。
那么有沒有一種既准確又方便的方法計算對象的大小呢?答案是肯定的。在Java 5中引入了Instrumentation類,這個類提供了計算對象內存占用量的方法;Hotspot支持instrumentation框架,其他的虛擬機也提供了類似的框架
使用
Instrumentation
類計算Java對象大小的過程如下:
- 創建一個有premain方法的agent 類,
- JVM在調用agent類的premain方法時會傳入一個Instrumentation 對象,調用Instrumentation的getObjectSize方法
- 把agent類打成一個jar包
- 啟動我們的應用程序,使用JVM參數指定agent jar的路徑
下面以計算Object對象的大小為例,詳細介紹使用Instrumentation計算對象大小的過程
1. 創建Instrumentation agent類
Instrumentation agent類有一個方法premain,聲明如下:
1 public static void premain(String args, Instrumentation inst) { 2 ... 3 }
JVM會在應用程序運行之前調用這個方法(也就是在執行應用程序的main方法之前),JVM會在調用該方法時傳入一個實現Instrumentation接口的實例,此時我們就可以調用getObjectSize()方法來計算對象的大小。例如我們要計算Object實例和自定義類型MyObject實例的大小,agent代碼如下:
1 package my; 2 import java.lang.instrument.Instrumentation; 3 4 5 public class MyAgent { 6 public static void premain(String args, Instrumentation inst) { 7 Object obj = new Object(); 8 System.out.println("Bytes used by Object:"+ inst.getObjectSize(obj)); 9 System.out.println("Bytes used by MyObject:"+ inst.getObjectSize(new MyObject())); 10 } 11 public static void main(String[] args) { 12 System.out.println("main is over"); 13 } 14 }
MyObject代碼如下:
1 package my; 2 3 public class MyObject{ 4 Object object = new Object(); 5 }
需要注意的是agent類不需要實現任何接口,只需要定義premain方法就行,JVM會自動調用該方法。
2. 把agent類打包成jar包
在打包之前需要創建manifest 文件,創建manifest.txt文件,包括以下內容:
Premain-Class: my.MyAgent
然后執行一下命令創建jar包
jar -cmf manifest.txt agent.jar my/*
3.使用agent運行應用程序
運行應用程序,並使用javaagent命令行參數指定instrumentation agent的jar文件,加入classpath為當前目錄並且main方法在com.mypackage.Main中,命令如下:
java -javaagent:agent.jar -cp . my.MyAgent
在32位機器上運行結果如下:
hadoop@32bithost:~/workspace/my/bin$ java -javaagent:agent.jar my.MyAgent
Bytes used by Object:8
Bytes used by MyObject:16
main is over
在64位機器上(不開啟指針壓縮)運行結果如下:
[genie.yjd@64bithost ~]$ java -XX:-UseCompressedOops -javaagent:agent.jar -cp . my.MyAgent Bytes used by Object:16 Bytes used by MyObject:24 main is over
在64位機器上(開啟指針壓縮)運行結果如下:
[genie.yjd@64bithost~]$ java -XX:+UseCompressedOops -javaagent:agent.jar -cp . my.MyAgent
Bytes used by Object:16
Bytes used by MyObject:16
main is over
運行結果顯示對於Object對象在32bit機器上占8個字節,在64bit機器上占16個字節,而對於用於一個Object類型成員的MyObject在32bit機器上占用16個字節,而在64bit機器上不開啟指針壓縮是占用24個字節,開啟指針壓縮后占用16個字節。
在應用程序中訪問Instrumentation對象
在上面的例子中,我們在premain方法中計算對象的大小。可是如果我們想要在應用程序執行期間計算某個對象的大小該怎么辦呢?
我們可以這樣做,在premain方法中把Instrumentation對象保存在一個static引用中,然后提供一個static方法訪問這個實例,代碼如下:
1 public class MyAgent { 2 private static volatile Instrumentation globalInstr; 3 public static void premain(String args, Instrumentation inst) { 4 globalInstr = inst; 5 } 6 public static long getObjectSize(Object obj) { 7 if (globalInstr == null) 8 throw new IllegalStateException("Agent not initted"); 9 return globalInstr.getObjectSize(obj); 10 } 11 }
這樣我們就可以在應用程序中調用 MyAgent.getObjectSize()來計算運行時任意實例的大小了。
深度計算對象內存使用
注意getObjectSize方法不包括對象應用的其他對象的大小。加入對象A引用對象B,使用getObjectSize()方法計算對象A的大小時,只包括對象B引用的大小(4byte),而不是對象B的真是大小。如何深度計算對象的大小,我會在下一篇blog中詳細說明。