一個Java物件到底佔用多大記憶體?
更多文章,可以關注公眾號,第一時間送達。

最近在調研MAT和VisualVM原始碼實現,遇到一個可疑問題,兩者計算出來的物件大小不一致,該信哪個?
為了復現這個問題,準備了4個簡單類:
class AAAAA {}
class BBBBB { int a = 1; }
class CCCCC { long a = 1L; }
class DDDDD { String s = "hello"; }
再來個主函式:
final List<AAAAA> aaa = new ArrayList<>(100000); final List<BBBBB> bbb = new ArrayList<>(100000); final List<CCCCC> ccc = new ArrayList<>(100000); final List<DDDDD> ddd = new ArrayList<>(100000); for (int i = 0; i < 100000; i++) { aaa.add(new AAAAA()); bbb.add(new BBBBB()); ccc.add(new CCCCC()); ddd.add(new DDDDD()); }
本地環境是64位的JDK8,預設的啟動引數,執行之後通過 jmap -dump
命令生成dump檔案,分別用MAT和VisualVM開啟。
MAT

通過MAT開啟,可以發現ABD物件大小都是16位元組,反而C物件大小為24位元組
VisualVM

通過VisualVM開啟,可以發現顯示的大小和MAT的有蠻大的差別。
哪個才是正確的?
要回答這個問題,首先得清楚的知道JVM中一個物件的記憶體佈局。
JVM中一個物件包含3個部分:物件頭、例項資料和對齊填充。
物件頭
這裡不講物件頭是個什麼東西,感興趣的同學可以看我的其它文章。
物件頭的大小一般和系統的位數有關,也和啟動引數 UseCompressedOops
有關:
UseCompressedOops
例項資料
原生型別的記憶體佔用情況如下:
- boolean 1
- byte 1
- short 2
- char 2
- int 4
- float 4
- long 8
- double 8
引用型別的記憶體佔用和系統位數以及啟動引數 UseCompressedOops
有關
UseCompressedOops
對齊填充
在Hotspot中,為了更加容易的管理記憶體,一般會使用8位元組進行對齊。
意思是每次分配的記憶體大小一定是8的倍數,如果物件頭+例項資料的值不是8的倍數,那麼會重新計算一個較大值,進行分配。
結果
有了物件各部分的記憶體佔用大小,可以很輕鬆的計算出ABCD各物件在64位系統,且開啟 UseCompressedOops
引數時的大小。
- 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的顯示結果有問
UseCompressedOops
感興趣的同學,可以動手實踐一下,加深對物件記憶體佈局的印象。
經過這段時間對MAT和VisualVM原始碼的研究,發現MAT的功能不是強大一點點,建議大家以後都使用MAT對dump檔案進行分析。