大家都知道,jvm中對象實例存儲在堆中,對象的引用存儲在棧中,而對象的元數據(類型數據)存儲在方法區。在我們進行內存優化的過程中經常需要了解每個對象占用的內存大小。接下來我將介紹對象占用內存大小的計算方式。
Java的對象模型
java是面向對象的語言,每個對象都屬于某個類。在HotSpot虛擬機中對象采用的是oop-klass模型。其實原理很簡單:就是在方法區中生成一個Class類保存類信息(Klass),包含靜態常量、靜態方法、字節碼、即時編譯代碼等元數據,而在堆中實例化該類的實例對象(oop),實例對象中保存了指向Class類的指針,這樣便構成了oop-klass模型。這樣做有一個好處就是:在實現多態時只需要在Class類中保存虛方法表來減少頻繁的方法搜索,而實例對象無需保存虛方法表。
每個對象都有一個 mark work 頭部,以及一個引用(klass pointer)指向Class類的信息。
- 在未開啟 UseCompressedOops 的 64 位 JVM 上,對象頭有 16 字節大小,即 8 字節的 mark word 和 8 字節的引用。
- 在開啟 UseCompressedOops 的 64 位機器上,引用成了 4 字節,一共 12 字節。
java對象在內存中模型如下:
Java對象內存占用
對象大小分為:
- 自身的大小(Shadow heap size)
- 所引用對象的大小(Retained heap size)
class MyClass {int a;Object object;
}
如上圖例子所示:myClass 實例創建出來之后,在內存中所占的大小就是 myClass 自身大小(Shadow heap size)。包括類的頭部大小以及一個int的大小和一個引用的大小。myClass 中object 成員變量是一個對象引用,這個被引用的對象也占一定大小。myClass 實例所維護的引用的對象所占的大小,稱為myClass實例的Retained heap size。我們這里僅討論如何計算對象自身的大小,引用對象大小的計算方式可依此類推。
java對象內存可分為:頭部 + 數據 + 對齊字節
±-----------------±-----------------±----------------- ±--------------+
| mark word | klass pointer | data (opt) | padding |
±-----------------±-----------------±------------------±--------------+
(1)頭部大小(mark word + klass pointer)
- 在未開啟 UseCompressedOops 的 64 位 JVM 上,對象頭有 16 字節大小,即 8 字節的 mark word 和8 字節的引用。
- 在開啟 UseCompressedOops 的 64 位機器上,引用成了 4 字節,一共 12 字節。
(2)數據大小(data)
空對象不包含任何成員變量,其大小即對象頭大小。若存在成員成員,為了內存緊湊,成員在內存中的排列和聲明的順序可能不一致,這樣才能充分利用內存空間。這是因為在32位系統中,對象大小需要為4byte(32位)的整數倍,而在64位的系統中,對象需要為8byte(64位)的整數倍。如下例子:
class MyClass {byte a;int c;boolean d;long e;Object f;
}
其內存布局為:
值得一提的是,數組對象和普通對象存在一點小區別:數組多一個記錄數組長度的 int 類型(4byte)