Java對象內(nèi)存布局之謎

Java對象內(nèi)存布局之謎

一個Java對象在堆上除了成員信息,還有其他內(nèi)容嗎?他在堆上是如何布局的?接下來本文將以Hotspot為例分析Java對象內(nèi)存布局之謎。

堆中的Java對象

在Hotspot中一個Java對象包含如下三個部分:

  1. 對象頭
  2. 實例信息
  3. 對齊信息

對象頭

對象頭要分兩種類型:

  • 普通對象包含:Mark Word、Klass Pointer
  • 數(shù)組對象包含:Mark Word、Klass Pointer、Array Length

不同類型JVM下,對象頭每一部分占用內(nèi)存大小

數(shù)據(jù)類型 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)進(jìn)行壓縮。而數(shù)組長度不管在什么類型的JVM里都是32bit。

不同類型JVM下,對象頭占用內(nèi)存大小

數(shù)據(jù)類型 32位JVM(bit) 64位JVM(bit) 開啟指針壓縮的64位JVM(bit)
普通對象 64 128 96
數(shù)組對象 96 160 128

由此可見,對象頭還是比較耗空間的。那么用了這么多內(nèi)存,對象頭具體都存放了寫什么信息呢?

mark word

mark word里存放的是對象運行時的信息,不同狀態(tài)的對象里mark word 存放的信息是不同的。具體內(nèi)容可看下表:

32位JVM

存儲內(nèi)容(30bit) 鎖狀態(tài)(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標(biāo)記

64位JVM

存儲內(nèi)容(62bit) 鎖狀態(tài)(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標(biāo)記
  1. 名詞解釋:

    • age: GC分代年齡
    • identify_hashcode: 對象的hashcode值
    • threadId: 偏向線程的Id
    • biased_lock: 是否是偏向鎖,因為只占一個bit,所以只有0和1
    • epoch: 偏向時間戳
    • ptr_to_lock_record: 指向棧中輕量級鎖記錄的指針
    • ptr_to_heavyweight_monitor:指向棧中重量級鎖的指針
    • GC標(biāo)記: 用于GC算法對對象的標(biāo)記
    • gc_info: GC算法給不同狀態(tài)的標(biāo)記信息
  2. 為什么要這么實現(xiàn)?

    1. 因為對象頭信息是跟對象自身定義的數(shù)據(jù)結(jié)構(gòu)無關(guān)的。這些信息所記錄的狀態(tài)是用于JVM對對象的管理的。更重要的是,不同狀態(tài)的存儲內(nèi)容基本上是互斥的。所以基于節(jié)省空間的角度考慮,Mark Word 被設(shè)計成動態(tài)的。
  3. identify_hashcode 既然有方法可以生成為什么要放在對象頭里?

    1. 當(dāng)一個對象的hashCode()未被重寫時,調(diào)用這個方法會返回一個由隨機(jī)數(shù)算法生成的值。因為一個對象的hashCode不可變,所以需要存到對象頭中。當(dāng)再次調(diào)用該方法時,會直接返回對象頭中的hashcode。
    2. identify_hashcode 采用延遲加載的方式生成。只有調(diào)用hashcode()時,才會寫入對象頭。若一個類的hashCode()方法被重寫,對象頭中將不存儲hashcode信息,因為一般我們自己實現(xiàn)的hashcode()并未將生成的值寫入對象頭。
  4. 當(dāng)對象的狀態(tài)不是默認(rèn)狀態(tài)時,對象的hashcode去哪兒了?

    1. 當(dāng)是輕量級鎖/重量級鎖時,jvm會將對象的 mark word 復(fù)制一份到棧幀的Lock Record中。 等線程釋放該對象時,再重新復(fù)制給對象。
    2. 如果一個對象頭中存在hashcode,則無法使用偏向鎖。

Klass Pointer

類型指針存放的是該對象對應(yīng)的類的指針。即該指針應(yīng)該指向方法區(qū)的內(nèi)存區(qū)域。

Array Length

數(shù)組長度只在數(shù)組類型的對象中存在。用于記錄數(shù)組的長度。避免獲取數(shù)組長度時,動態(tài)計算。以空間換時間。

實例信息

該部分存儲了一個類定義的所有的數(shù)據(jù)類型信息,包含從父類中繼承的信息。

分配策略

  • 相同寬度的字段放在一起
  • 父類的字段在前,子類的字段在后
  • 若設(shè)置CompactFields=true,則子類窄類型的變量也可能插入到父類的變量的空隙中

對齊信息

由于HotSpot規(guī)定對象的大小必須是8的整數(shù)倍,而對象頭剛好是8的整數(shù)倍,如果對象實例數(shù)據(jù)這部分不是的話,就需要占位符對齊填充。

參考

  • <<深入理解Java虛擬機(jī): JVM高級特性與最佳實踐>>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容