1. 程式人生 > 程式設計 >Java物件記憶體佈局之謎

Java物件記憶體佈局之謎

一個Java物件在堆上除了成員資訊,還有其他內容嗎?他在堆上是如何佈局的?接下來本文將以Hotspot為例分析Java物件記憶體佈局之謎。

堆中的Java物件

在Hotspot中一個Java物件包含如下三個部分:

  1. 物件頭
  2. 例項資訊
  3. 對齊資訊

物件頭

物件頭要分兩種型別:

  • 普通物件包含:Mark Word、Klass Pointer
  • 陣列物件包含:Mark Word、Klass Pointer、Array Length

不同型別JVM下,物件頭每一部分佔用記憶體大小

資料型別 32位JVM(bit) 64位JVM(bit) 開啟指標壓縮的64位JVM(bit)
Mark Word 32 64 64
Klass Pointer 32 64 32
Array Length 32 32 32

可見在64位JVM中開啟指標壓縮(-XX:UseCompressedOops)後,JVM只是針對型別指標(Klass Pointer)進行壓縮。而陣列長度不管在什麼型別的JVM裡都是32bit。

不同型別JVM下,物件頭佔用記憶體大小

資料型別 32位JVM(bit) 64位JVM(bit) 開啟指標壓縮的64位JVM(bit)
普通物件 64 128 96
陣列物件 96 160 128

由此可見,物件頭還是比較耗空間的。那麼用了這麼多記憶體,物件頭具體都存放了寫什麼資訊呢?

mark word

mark word裡存放的是物件執行時的資訊,不同狀態的物件裡mark word 存放的資訊是不同的。具體內容可看下錶:

32位JVM

儲存內容(30bit) 鎖狀態(2bit)
identify_hashcode:25 | age:4 | biased_lock:1 (01)無鎖
threadId:23 | age:4 | epoch:2 | biased_lock:1 (01)偏向鎖
ptr_to_lock_record:30 (00)輕量級鎖
ptr_to_heavyweight_monitor:30 (10)重量級鎖
gc_info:30 (11)GC標記

64位JVM

儲存內容(62bit) 鎖狀態(2bit)
unused:25 | identify_hashcode:25 | unused:1 | age:4 | biased_lock:1 (01)無鎖
threadId:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 (01)偏向鎖
ptr_to_lock_record:62 (00)輕量級鎖
ptr_to_heavyweight_monitor:62 (10)重量級鎖
gc_info:62 (11)GC標記
  1. 名詞解釋:

    • age: GC分代年齡
    • identify_hashcode: 物件的hashcode值
    • threadId: 偏向執行緒的Id
    • biased_lock: 是否是偏向鎖,因為只佔一個bit,所以只有0和1
    • epoch: 偏向時間戳
    • ptr_to_lock_record: 指向棧中輕量級鎖記錄的指標
    • ptr_to_heavyweight_monitor:指向棧中重量級鎖的指標
    • GC標記: 用於GC演演算法對物件的標記
    • gc_info: GC演演算法給不同狀態的標記資訊
  2. 為什麼要這麼實現?

    1. 因為物件頭資訊是跟物件自身定義的資料結構無關的。這些資訊所記錄的狀態是用於JVM對物件的管理的。更重要的是,不同狀態的儲存內容基本上是互斥的。所以基於節省空間的角度考慮,Mark Word 被設計成動態的。
  3. identify_hashcode 既然有方法可以生成為什麼要放在物件頭裡?

    1. 當一個物件的hashCode()未被重寫時,呼叫這個方法會返回一個由隨機數演演算法生成的值。因為一個物件的hashCode不可變,所以需要存到物件頭中。當再次呼叫該方法時,會直接返回物件頭中的hashcode。
    2. identify_hashcode 採用延遲載入的方式生成。只有呼叫hashcode()時,才會寫入物件頭。若一個類的hashCode()方法被重寫,物件頭中將不儲存hashcode資訊,因為一般我們自己實現的hashcode()並未將生成的值寫入物件頭。
  4. 當物件的狀態不是預設狀態時,物件的hashcode去哪兒了?

    1. 當是輕量級鎖/重量級鎖時,jvm會將物件的 mark word 複製一份到棧幀的Lock Record中。 等執行緒釋放該物件時,再重新複製給物件。
    2. 如果一個物件頭中存在hashcode,則無法使用偏向鎖。

Klass Pointer

型別指標存放的是該物件對應的類的指標。即該指標應該指向方法區的記憶體區域。

Array Length

陣列長度只在陣列型別的物件中存在。用於記錄陣列的長度。避免獲取陣列長度時,動態計算。以空間換時間。

例項資訊

該部分儲存了一個類定義的所有的資料型別資訊,包含從父類中繼承的資訊。

分配策略

  • 相同寬度的欄位放在一起
  • 父類的欄位在前,子類的欄位在後
  • 若設定CompactFields=true,則子類窄型別的變數也可能插入到父類的變數的空隙中

對齊資訊

由於HotSpot規定物件的大小必須是8的整數倍,而物件頭剛好是8的整數倍,如果物件例項資料這部分不是的話,就需要佔位符對齊填充。

參考

  • <<深入理解Java虛擬機器器: JVM高階特性與最佳實踐>>