Java物件記憶體佈局之謎
阿新 • • 發佈:2019-12-31
一個Java物件在堆上除了成員資訊,還有其他內容嗎?他在堆上是如何佈局的?接下來本文將以Hotspot為例分析Java物件記憶體佈局之謎。
堆中的Java物件
在Hotspot中一個Java物件包含如下三個部分:
- 物件頭
- 例項資訊
- 對齊資訊
物件頭
物件頭要分兩種型別:
- 普通物件包含: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標記 |
-
名詞解釋:
- 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演演算法給不同狀態的標記資訊
-
為什麼要這麼實現?
- 因為物件頭資訊是跟物件自身定義的資料結構無關的。這些資訊所記錄的狀態是用於JVM對物件的管理的。更重要的是,不同狀態的儲存內容基本上是互斥的。所以基於節省空間的角度考慮,Mark Word 被設計成動態的。
-
identify_hashcode 既然有方法可以生成為什麼要放在物件頭裡?
- 當一個物件的hashCode()未被重寫時,呼叫這個方法會返回一個由隨機數演演算法生成的值。因為一個物件的hashCode不可變,所以需要存到物件頭中。當再次呼叫該方法時,會直接返回物件頭中的hashcode。
- identify_hashcode 採用延遲載入的方式生成。只有呼叫hashcode()時,才會寫入物件頭。若一個類的hashCode()方法被重寫,物件頭中將不儲存hashcode資訊,因為一般我們自己實現的hashcode()並未將生成的值寫入物件頭。
-
當物件的狀態不是預設狀態時,物件的hashcode去哪兒了?
- 當是輕量級鎖/重量級鎖時,jvm會將物件的 mark word 複製一份到棧幀的Lock Record中。 等執行緒釋放該物件時,再重新複製給物件。
- 如果一個物件頭中存在hashcode,則無法使用偏向鎖。
Klass Pointer
型別指標存放的是該物件對應的類的指標。即該指標應該指向方法區的記憶體區域。
Array Length
陣列長度只在陣列型別的物件中存在。用於記錄陣列的長度。避免獲取陣列長度時,動態計算。以空間換時間。
例項資訊
該部分儲存了一個類定義的所有的資料型別資訊,包含從父類中繼承的資訊。
分配策略
- 相同寬度的欄位放在一起
- 父類的欄位在前,子類的欄位在後
- 若設定CompactFields=true,則子類窄型別的變數也可能插入到父類的變數的空隙中
對齊資訊
由於HotSpot規定物件的大小必須是8的整數倍,而物件頭剛好是8的整數倍,如果物件例項資料這部分不是的話,就需要佔位符對齊填充。
參考
- <<深入理解Java虛擬機器器: JVM高階特性與最佳實踐>>