介紹兩種獲取JAVA對象內存大小的方法。
第一種:Instrumentation
簡介:
使用java.lang.instrument 的Instrumentation來獲取一個對象的內存大小。利用Instrumentation並且通過代理我們可以監測在JVM運行的程序的功能,它的原理是修改方法的字節碼。
首先創建代理類
package com.dingtongblog.size; import java.lang.instrument.Instrumentation; public class ObjectSize { private static volatile Instrumentation instru; public static void premain(String args, Instrumentation inst) { instru = inst; } public static Long getSizeOf(Object object) { if (instru == null) { throw new IllegalStateException("Instrumentation is null"); } return instru.getObjectSize(object); } }
premain方法:JVM會首先調用這個方法。通過這個方法我們就可以把屬性instru初始化化成功,通過Instrumentation的getObjectSize(Object object)方法我們就獲取一個對象的大小了。
然后把這個類打包成jar包
首先我們要創建manifest.txt,並且增加這樣的一行
Premain-Class:com.dingtongblog.size.ObjectSize
這個premain-Class指定了是哪個是代理類,也就是包括了premain方法的類。
然后把ObjectSize打包成jar包
java -cmf manifest.txt simpleSize.jar com/dingtongblog/size/ObjectSize.class
運行
然后把jar引入到工程中, 並且啟動參數加入
-javaagent:jarpath[=options]
在命令行中執行
java -javaagent:simpleSize.jar TestMain
(當前TestMain和simpleSize.jar在同一目錄下)
測試代碼如下:
import com.dingtongblog.size.ObjectSize; public class TestMain { public static void main(String[] args) { String a = new String(aa); System.out.println(ObjectSize.getSizeOf(a)); } }
輸出24
我們修改參數a
String a = aa; 改成
String a = aaaa;
然后再運行main,還是輸出24。
為什么值會沒有改變呢?這里雖然a指向的對象已經改變了,但是輸出的值還是24沒變。這是因為這個使用getSizeOf這個方法取得的對象本身的內存大小,不包含對象中屬性所指向的對象的大小。在
String 中一共有3個
int 屬性,1個數組的引用,再加上對象頭占的字節數為 3 * 4 + 4 +8 = 24 ,然后24正好是8的倍數,不需要填充字節。所以直接輸出24。
有辦法可以取到一個對象完整的字節數嗎?有一種思路是通過反射遍歷對象中每個屬性,然后調用上述的方法得到每個對象大小,把得到得對象再重復上面的過程,直到最后指向的是基本類型。直接引用
這篇文章中的jar包。
引入包,並且啟動JVM的時候加上參數
-javaagent:D:\sizeofag.jar
測試代碼:
public class TestMain { public static void main(String[] args) throws IllegalAccessException { String a = new String(aa); System.out.println(SizeOfAgent.fullSizeOf(a)); System.out.println(SizeOfAgent.sizeOf(a)); }
可以看到輸出的結果40,24
40就是這個對象的完整大小。首先是前面計算的24字節+數組對象頭的12個字節+兩個字符字節2*2 可以得到24+12+4 =40。40是8個倍數,不需要填充字節。
第二種是jmap ,jhat命令
jmap 可以輸出給定的程序中堆的詳情。
jmap -histo <pid> (pid為當前JAVA進程的id)
例如
jmap -histo 20230
這樣就直接輸出當前堆的詳細情況,但是這樣不太直觀。
通過
jmap -dump:format=b,file=<filename>
可以把java的堆以hprof 二進制格式輸出到一個文件中,然后通過jhat命令來查看,jhat會生成一個頁面,能比較直觀的查看堆詳情。但是jhat需要的內存空間為dump文件的幾倍,如果dump文件比較大會遇到OOM錯誤,這時候可以通過MAT來瀏覽堆信息。
例如
jmap -dump:format=b,file=d:\dump.txt
然后通過
jhat filename ;
(filename為之前dump出來的文件) 會解析JAVA 堆的DUMP文件並且會啟動一個web服務器,服務器的默認端口為7000,命令執行完之后就可以通過127.0.0.1:7000訪問堆詳情了。
jhat d:\dump.txt
可以得到類似這樣的頁面。
不過通過這個方法得到的計算結果和之前用的instrumentation方法得到的結果還不一樣。這個地方可能計算的方法有區別,個人覺得instrumentation的結果會比較准,但是還沒找到有關的資料說明這個問題。
另外使用Jprofiler工具也可以監控內存使用的詳細情況。
資料:
http://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html instrumentation介紹
http://www.javamex.com/tutorials/memory/instrumentation.shtml instrumentation使用