Java對象內存布局之謎
一個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高級特性與最佳實踐>>