最近在調研MAT和VisualVM源碼實現,遇到一個可疑問題,兩者計算出來的對象大小不一致,才有了這樣疑惑。
一個Java對象到底佔用多大內存?

為了復現這個問題,準備了4個最簡單類:
<code>class AAAAA {}/<code>
<code>class BBBBB {/<code>
<code>int a = 1;/<code>
<code>}/<code>
<code>class CCCCC {/<code>
<code>long a = 1L;/<code>
<code>}/<code>
<code>class DDDDD {/<code>
<code>Strings ="hello";/<code>
<code>}/<code>
當然了,再來個主函數:
<code>final List<aaaaa> aaa = newArrayList<>(100000);/<aaaaa>/<code>
<code>final List<bbbbb> bbb = newArrayList<>(100000);/<bbbbb>/<code>
<code>final List<ccccc> ccc = newArrayList<>(100000);/<ccccc>/<code>
<code>final List<ddddd> ddd = newArrayList <>(100000);/<ddddd>/<code>
<code>for (int i = 0; i <100000; i++) {/<code>
<code>aaa.add(new AAAAA);/<code>
<code>bbb.add(new BBBBB);/<code>
<code>ccc.add(new CCCCC);/<code>
<code>ddd.add(new DDDDD);/<code>
<code>}/<code>
本地的執行環境是64位的JDK8,且使用默認的啟動參數,運行之後通過 <code>jmap-dump/<code>命令生成dump文件,分別用MAT和VisualVM打開。
MAT

通過MAT打開,可以發現ABD對象大小都是16字節,而C對象大小為24字節
VisualVM
通過Vis打開,可以發現其顯示的大小和MAT有蠻大的差別。
好奇怪,哪個是對的?
要回答這個問題,首先得清楚的知道JVM中對象的內存佈局。
在Hotspot中,一個對象包含3個部分:對象頭、實例數據和對齊填充。
對象頭
這裡不講對象頭是個什麼東西,感興趣的同學可以看我的其它文章。對象頭的大小一般和系統的位數有關,也和啟動參數 <code>UseCompressedOops/<code>有關:
32位系統,佔用 8 字節
64位系統,開啟 <code>UseCompressedOops/<code>時,佔用 12 字節,否則是16字節
實例數據
原生類型的內存佔用情況如下:
boolean 1個字節
byte 1個字節
short 2個字節
char 2個字節
int 4個字節
float 4個字節
long 8個字節
double 8個字節
引用類型的內存佔用和系統位數以及啟動參數 <code>UseCompressedOops/<code>有關
32位系統佔4字節
-
64位系統,開啟 <code>UseCompressedOops/<code>時,佔用4字節,否則是8字節
對齊填充
在Hotspot中,為了更加容易的管理內存,一般會使用8字節進行對齊。
意思是每次分配的內存大小一定是8的倍數,如果對象頭+實例數據的值不是8的倍數,那麼會重新計算一個較大值,進行分配。
結果
有了對象各部分的內存佔用大小,可以很輕鬆的計算出ABCD各對象在64位系統,且開啟 <code>UseCompressedOops/<code>參數時的大小。
A對象只包含一個對象頭,大小佔12字節,不是8的倍數,需要4字節進行填充,一共佔16字節
-
B對象包含一個對象頭和int類型,12+4=16,正好是8的倍數,不需要填充。
C對象包含一個對象頭和long類型,12+8=20,不是8的倍數,需要4個字節進行填充,佔24字節
D對象包含一個對象頭和引用類型,12+4=16,正好是8的倍數,不需要填充。
可以得出,VisualVM的顯示結果有點問題,主要因為以下兩點:
首先,沒有考慮是否開啟 <code>UseCompressedOops;/<code>
其次,沒有考慮內存對齊填充的情況;
感興趣的同學,可以動手實踐一下,這樣可以加深對象內存佈局的理解。
經過這段時間對MAT和VisualVM的源碼研究,發現MAT的功能不是強大一點點,建議大家以後儘量使用MAT。
閱讀更多 阿飛的BLOG 的文章