Java對(duì)象內(nèi)存布局之謎
一個(gè)Java對(duì)象在堆上除了成員信息丸相,還有其他內(nèi)容嗎?他在堆上是如何布局的?接下來(lái)本文將以Hotspot為例分析Java對(duì)象內(nèi)存布局之謎妨退。
堆中的Java對(duì)象
在Hotspot中一個(gè)Java對(duì)象包含如下三個(gè)部分:
- 對(duì)象頭
- 實(shí)例信息
- 對(duì)齊信息
對(duì)象頭
對(duì)象頭要分兩種類型:
- 普通對(duì)象包含:Mark Word讽坏、Klass Pointer
- 數(shù)組對(duì)象包含:Mark Word锭魔、Klass Pointer、Array Length
不同類型JVM下路呜,對(duì)象頭每一部分占用內(nèi)存大小
數(shù)據(jù)類型 | 32位JVM(bit) | 64位JVM(bit) | 開(kāi)啟指針壓縮的64位JVM(bit) |
---|---|---|---|
Mark Word | 32 | 64 | 64 |
Klass Pointer | 32 | 64 | 32 |
Array Length | 32 | 32 | 32 |
可見(jiàn)在64位JVM中開(kāi)啟指針壓縮(-XX:UseCompressedOops)后, JVM只是針對(duì)類型指針(Klass Pointer)進(jìn)行壓縮迷捧。而數(shù)組長(zhǎng)度不管在什么類型的JVM里都是32bit织咧。
不同類型JVM下,對(duì)象頭占用內(nèi)存大小
數(shù)據(jù)類型 | 32位JVM(bit) | 64位JVM(bit) | 開(kāi)啟指針壓縮的64位JVM(bit) |
---|---|---|---|
普通對(duì)象 | 64 | 128 | 96 |
數(shù)組對(duì)象 | 96 | 160 | 128 |
由此可見(jiàn)漠秋,對(duì)象頭還是比較耗空間的笙蒙。那么用了這么多內(nèi)存,對(duì)象頭具體都存放了寫(xiě)什么信息呢庆锦?
mark word
mark word里存放的是對(duì)象運(yùn)行時(shí)的信息捅位,不同狀態(tài)的對(duì)象里mark word 存放的信息是不同的。具體內(nèi)容可看下表:
32位JVM
存儲(chǔ)內(nèi)容(30bit) | 鎖狀態(tài)(2bit) |
---|---|
identify_hashcode:25 | age:4 | biased_lock:1 | (01)無(wú)鎖 |
threadId:23 | age:4 | epoch:2 | biased_lock:1 | (01)偏向鎖 |
ptr_to_lock_record:30 | (00)輕量級(jí)鎖 |
ptr_to_heavyweight_monitor:30 | (10)重量級(jí)鎖 |
gc_info:30 | (11)GC標(biāo)記 |
64位JVM
存儲(chǔ)內(nèi)容(62bit) | 鎖狀態(tài)(2bit) |
---|---|
unused:25 | identify_hashcode:25 | unused:1 | age:4 | biased_lock:1 | (01)無(wú)鎖 |
threadId:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | (01)偏向鎖 |
ptr_to_lock_record:62 | (00)輕量級(jí)鎖 |
ptr_to_heavyweight_monitor:62 | (10)重量級(jí)鎖 |
gc_info:62 | (11)GC標(biāo)記 |
-
名詞解釋:
- age: GC分代年齡
- identify_hashcode: 對(duì)象的hashcode值
- threadId: 偏向線程的Id
- biased_lock: 是否是偏向鎖搂抒,因?yàn)橹徽家粋€(gè)bit,所以只有0和1
- epoch: 偏向時(shí)間戳
- ptr_to_lock_record: 指向棧中輕量級(jí)鎖記錄的指針
- ptr_to_heavyweight_monitor:指向棧中重量級(jí)鎖的指針
- GC標(biāo)記: 用于GC算法對(duì)對(duì)象的標(biāo)記
- gc_info: GC算法給不同狀態(tài)的標(biāo)記信息
-
為什么要這么實(shí)現(xiàn)艇搀?
- 因?yàn)閷?duì)象頭信息是跟對(duì)象自身定義的數(shù)據(jù)結(jié)構(gòu)無(wú)關(guān)的。這些信息所記錄的狀態(tài)是用于JVM對(duì)對(duì)象的管理的求晶。更重要的是焰雕,不同狀態(tài)的存儲(chǔ)內(nèi)容基本上是互斥的。所以基于節(jié)省空間的角度考慮誉帅,Mark Word 被設(shè)計(jì)成動(dòng)態(tài)的淀散。
-
identify_hashcode 既然有方法可以生成為什么要放在對(duì)象頭里?
- 當(dāng)一個(gè)對(duì)象的hashCode()未被重寫(xiě)時(shí)蚜锨,調(diào)用這個(gè)方法會(huì)返回一個(gè)由隨機(jī)數(shù)算法生成的值档插。因?yàn)橐粋€(gè)對(duì)象的hashCode不可變,所以需要存到對(duì)象頭中亚再。當(dāng)再次調(diào)用該方法時(shí)郭膛,會(huì)直接返回對(duì)象頭中的hashcode。
- identify_hashcode 采用延遲加載的方式生成氛悬。只有調(diào)用hashcode()時(shí)则剃,才會(huì)寫(xiě)入對(duì)象頭。若一個(gè)類的hashCode()方法被重寫(xiě)如捅,對(duì)象頭中將不存儲(chǔ)hashcode信息棍现,因?yàn)橐话阄覀冏约簩?shí)現(xiàn)的hashcode()并未將生成的值寫(xiě)入對(duì)象頭。
-
當(dāng)對(duì)象的狀態(tài)不是默認(rèn)狀態(tài)時(shí)镜遣,對(duì)象的hashcode去哪兒了己肮?
- 當(dāng)是輕量級(jí)鎖/重量級(jí)鎖時(shí),jvm會(huì)將對(duì)象的 mark word 復(fù)制一份到棧幀的Lock Record中悲关。 等線程釋放該對(duì)象時(shí)谎僻,再重新復(fù)制給對(duì)象。
- 如果一個(gè)對(duì)象頭中存在hashcode,則無(wú)法使用偏向鎖寓辱。
Klass Pointer
類型指針存放的是該對(duì)象對(duì)應(yīng)的類的指針艘绍。即該指針應(yīng)該指向方法區(qū)的內(nèi)存區(qū)域。
Array Length
數(shù)組長(zhǎng)度只在數(shù)組類型的對(duì)象中存在秫筏。用于記錄數(shù)組的長(zhǎng)度诱鞠。避免獲取數(shù)組長(zhǎng)度時(shí)挎挖,動(dòng)態(tài)計(jì)算。以空間換時(shí)間般甲。
實(shí)例信息
該部分存儲(chǔ)了一個(gè)類定義的所有的數(shù)據(jù)類型信息,包含從父類中繼承的信息肋乍。
分配策略
- 相同寬度的字段放在一起
- 父類的字段在前鹅颊,子類的字段在后
- 若設(shè)置CompactFields=true,則子類窄類型的變量也可能插入到父類的變量的空隙中
對(duì)齊信息
由于HotSpot規(guī)定對(duì)象的大小必須是8的整數(shù)倍敷存,而對(duì)象頭剛好是8的整數(shù)倍,如果對(duì)象實(shí)例數(shù)據(jù)這部分不是的話堪伍,就需要占位符對(duì)齊填充锚烦。
參考
- <<深入理解Java虛擬機(jī): JVM高級(jí)特性與最佳實(shí)踐>>