Java 對象詳解(從JVM角度)
簡(jiǎn)介
JVM 內存詳解 已經(jīng)詳細介紹了 JVM 內存的結構,本文主要講講 Java 對象到底是什么樣子的,方便我們了解 Java 對象。
對象結構
對象頭區域
HotSpot 虛擬機的對象頭包含兩部分信息:
- 第一部分是用于存儲對象自身的運行時(shí)數據,如哈希碼、GC 分代年齡信息、鎖狀態(tài)標識、線(xiàn)程持有的鎖、偏向線(xiàn)程 ID、偏向時(shí)間戳等,這部分數據的長(cháng)度在 32 位和 64 位虛擬機中分別為 32Bit 和 64Bit,官方稱(chēng)為 “Mark Word”。對需要存儲的運行時(shí)數據很多,其實(shí)已經(jīng)超過(guò)了 32Bit 或者 64Bit Bitmap 結構所能記錄的長(cháng)度,但是對象頭信息是與對象自身定義的數據無(wú)關(guān)的額外存儲成本,考慮到虛擬機的空間效率,Mark Word 被設計成一個(gè)非固定的數據結構已便在極小的內存空間存儲盡量多的信息。它會(huì )根據對象的狀態(tài)復用自己的存儲空間。例如,在 32 位的 HotSpot 虛擬機中,如果對象處于未被鎖定的狀態(tài)下,那么 Mark Word 的 32Bit 空間中的 25bit 用于存儲哈希碼,4Bit 用于存儲對象的分代年齡標識,2Bit 用于存儲鎖標示位,1Bit 固定為 0.
- 第二部分是類(lèi)型指針,即對象指向它的類(lèi)元數據的指針,虛擬機通過(guò)這個(gè)指針來(lái)確定這個(gè)對象是哪個(gè)類(lèi)的實(shí)例。并不是所有的虛擬機實(shí)現都必須在對象數據上保留類(lèi)型指針,換句話(huà)說(shuō),查找對象的元數據信息并不一定要經(jīng)過(guò)對象本身。如果對象是一個(gè) Java 數組,那在對象頭中還必須有一塊用于記錄數組長(cháng)度的數據,因為虛擬機可以通過(guò)普通 Java 對象的元數據信息確定 Java 對象的大小,但是從數組的元數據中卻無(wú)法確定數據的大小。
實(shí)例數據
實(shí)例數據區域存儲對象有用信息,包含程序代碼中所定義的各種類(lèi)型的字段內容,無(wú)論是從父類(lèi)繼承下來(lái)的還是在子類(lèi)中定義的,都需要記錄下來(lái)。這部分的存儲順序會(huì )受到虛擬機分配策略參數和字段在 Java 源碼中定義的順序的影響。HotSpot 虛擬機默認的分配策略為 longs/doubles、ints、shorts/charts、bytes/booleans、oops(Ordinary Object Pointers),從分配策略中可以看出,相同寬度的字段總是被分配到一起。
對齊填充
對齊填充區域也是不一定存在的內存,因為 HotSpot 虛擬機要求對象大小必須是 8 整數倍,所以當對象頭與實(shí)例數據不滿(mǎn)足時(shí)需要這塊區域補充。
對象的訪(fǎng)問(wèn)定位
目前主流 JVM 訪(fǎng)問(wèn)對象的方式有兩種: 使用句柄和指針。
如果使用句柄方式的話(huà),那么 Java 堆中將會(huì )劃分出一塊內存來(lái)作為句柄池,reference 中存儲的就是對象的句柄地址,而句柄中包含了對象實(shí)例數據與類(lèi)型數據各自的具體地址信息,如下圖所示:
如果通過(guò)指針訪(fǎng)問(wèn)對象,那么 Java 堆對象中就必須考慮如何放置訪(fǎng)問(wèn)類(lèi)型數據的相關(guān)信息,而 reference 中存儲直接就是對象的地址,如下圖所示:
這兩種訪(fǎng)問(wèn)方式各有優(yōu)勢,使用句柄來(lái)訪(fǎng)問(wèn)的最大好處就是 reference 中存儲的是穩定的句柄地址,在對象被移動(dòng)(垃圾收集時(shí)移動(dòng)對象時(shí)非常普遍的行為)時(shí)只會(huì )改變句柄中的實(shí)例數據指針,而 reference 本身不需要修改;使用指針訪(fǎng)問(wèn)方式的最大好處就是速度更快,它節省了一次指針定位的時(shí)間開(kāi)銷(xiāo),由于對象的訪(fǎng)問(wèn)在 Java 中非常頻繁,因此這類(lèi)開(kāi)銷(xiāo)積少成多后也是一項非??捎^(guān)的執行成本。HotSpot 就是使用指針訪(fǎng)問(wèn)的方式。
參考文檔
