- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) > 編程語(yǔ)言 >
- 詳解Java對象的內存布局
今天來(lái)講些抽象的東西 -- 對象頭,因為我在學(xué)習的過(guò)程中發(fā)現很多地方都關(guān)聯(lián)到了對象頭的知識點(diǎn),例如JDK中的 synchronized鎖優(yōu)化 和 JVM 中對象年齡升級等等。要深入理解這些知識的原理,了解對象頭的概念很有必要,而且可以為后面分享 synchronized 原理和 JVM 知識的時(shí)候做準備。
Java 中通過(guò) new 關(guān)鍵字創(chuàng )建一個(gè)類(lèi)的實(shí)例對象,對象存于內存的堆中并給其分配一個(gè)內存地址,那么是否想過(guò)如下這些問(wèn)題:
在 JVM 中,Java對象保存在堆中時(shí),由以下三部分組成:
我們可以在Hotspot官方文檔中找到它的描述(下圖)。從中可以發(fā)現,它是Java對象和虛擬機內部對象都有的共同格式,由兩個(gè)字(計算機術(shù)語(yǔ))組成。另外,如果對象是一個(gè)Java數組,那在對象頭中還必須有一塊用于記錄數組長(cháng)度的數據,因為虛擬機可以通過(guò)普通Java對象的元數據信息確定Java對象的大小,但是從數組的元數據中無(wú)法確定數組的大小。
它里面提到了對象頭由兩個(gè)字組成,這兩個(gè)字是什么呢?我們還是在上面的那個(gè)Hotspot官方文檔中往上看,可以發(fā)現還有另外兩個(gè)名詞的定義解釋?zhuān)謩e是 mark word 和 klass pointer。
從中可以發(fā)現對象頭中那兩個(gè)字:第一個(gè)字就是 mark word,第二個(gè)就是 klass pointer。
Mark Word
用于存儲對象自身的運行時(shí)數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標志、線(xiàn)程持有的鎖、偏向線(xiàn)程ID、偏向時(shí)間戳等等。
Mark Word在32位JVM中的長(cháng)度是32bit,在64位JVM中長(cháng)度是64bit。我們打開(kāi)openjdk的源碼包,對應路徑/openjdk/hotspot/src/share/vm/oops
,Mark Word對應到C++的代碼markOop.hpp
,可以從注釋中看到它們的組成,本文所有代碼是基于Jdk1.8。
Mark Word在不同的鎖狀態(tài)下存儲的內容不同,在32位JVM中是這么存的
在64位JVM中是這么存的
雖然它們在不同位數的JVM中長(cháng)度不一樣,但是基本組成內容是一致的。
Klass Pointer
即類(lèi)型指針,是對象指向它的類(lèi)元數據的指針,虛擬機通過(guò)這個(gè)指針來(lái)確定這個(gè)對象是哪個(gè)類(lèi)的實(shí)例。
如果對象有屬性字段,則這里會(huì )有數據信息。如果對象無(wú)屬性字段,則這里就不會(huì )有數據。根據字段類(lèi)型的不同占不同的字節,例如boolean類(lèi)型占1個(gè)字節,int類(lèi)型占4個(gè)字節等等;
對象可以有對齊數據也可以沒(méi)有。默認情況下,Java虛擬機堆中對象的起始地址需要對齊至8的倍數。如果一個(gè)對象用不到8N個(gè)字節則需要對其填充,以此來(lái)補齊對象頭和實(shí)例數據占用內存之后剩余的空間大小。如果對象頭和實(shí)例數據已經(jīng)占滿(mǎn)了JVM所分配的內存空間,那么就不用再進(jìn)行對齊填充了。
所有的對象分配的字節總SIZE需要是8的倍數,如果前面的對象頭和實(shí)例數據占用的總SIZE不滿(mǎn)足要求,則通過(guò)對齊數據來(lái)填滿(mǎn)。
為什么要對齊數據?字段內存對齊的其中一個(gè)原因,是讓字段只出現在同一CPU的緩存行中。如果字段不是對齊的,那么就有可能出現跨緩存行的字段。也就是說(shuō),該字段的讀取可能需要替換兩個(gè)緩存行,而該字段的存儲也會(huì )同時(shí)污染兩個(gè)緩存行。這兩種情況對程序的執行效率而言都是不利的。其實(shí)對其填充的最終目的是為了計算機高效尋址。
至此,我們已經(jīng)了解了對象在堆內存中的整體結構布局,如下圖所示
Talk is cheap, show me code
概念的東西是抽象的,你說(shuō)它是這樣組成的,就真的是嗎?學(xué)習是需要持懷疑的態(tài)度的,任何理論和概念只有自己證實(shí)和實(shí)踐之后才能接受它。還好 openjdk 給我們提供了一個(gè)工具包,可以用來(lái)獲取對象的信息和虛擬機的信息,我們只需引入 jol-core 依賴(lài),如下
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.8</version> </dependency>
jol-core 常用的三個(gè)方法
ClassLayout.parseInstance(object).toPrintable()
:查看對象內部信息.GraphLayout.parseInstance(object).toPrintable()
:查看對象外部信息,包括引用的對象.GraphLayout.parseInstance(object).totalSize()
:查看對象總大小.普通對象
為了簡(jiǎn)單化,我們不用復雜的對象,自己創(chuàng )建一個(gè)類(lèi) D,先看無(wú)屬性字段的時(shí)候
public class D { }
通過(guò) jol-core 的 api,我們將對象的內部信息打印出來(lái)
public static void main(String[] args) { D d = new D(); System.out.println(ClassLayout.parseInstance(d).toPrintable()); }
最后的打印結果為
可以看到有 OFFSET、SIZE、TYPE DESCRIPTION、VALUE 這幾個(gè)名詞頭,它們的含義分別是
可以看到,d對象實(shí)例共占據16byte,對象頭(object header)占據12byte(96bit),其中 mark word占8byte(64bit),klass pointe 占4byte,另外剩余4byte是填充對齊的。
這里由于默認開(kāi)啟了指針壓縮,所以對象頭占了12byte,具體的指針壓縮的概念這里就不再闡述了,感興趣的讀者可以自己查閱下官方文檔。jdk8版本是默認開(kāi)啟指針壓縮的,可以通過(guò)配置vm參數開(kāi)啟關(guān)閉指針壓縮,-XX:-UseCompressedOops
。
如果關(guān)閉指針壓縮重新打印對象的內存布局,可以發(fā)現總SIZE變大了,從下圖中可以看到,對象頭所占用的內存大小變?yōu)?6byte(128bit),其中 mark word占8byte,klass pointe 占8byte,無(wú)對齊填充。
開(kāi)啟指針壓縮可以減少對象的內存使用。從兩次打印的D對象布局信息來(lái)看,關(guān)閉指針壓縮時(shí),對象頭的SIZE增加了4byte,這里由于D對象是無(wú)屬性的,讀者可以試試增加幾個(gè)屬性字段來(lái)看下,這樣會(huì )明顯的發(fā)現SIZE增長(cháng)。因此開(kāi)啟指針壓縮,理論上來(lái)講,大約能節省百分之五十的內存。jdk8及以后版本已經(jīng)默認開(kāi)啟指針壓縮,無(wú)需配置。
數組對象
上面使用的是普通對象,我們來(lái)看下數組對象的內存布局,比較下有什么異同
public static void main(String[] args) { int[] a = {1}; System.out.println(ClassLayout.parseInstance(a).toPrintable()); }
打印的內存布局信息,如下
可以看到這時(shí)總SIZE為共24byte,對象頭占16byte,其中Mark Work占8byte,Klass Point 占4byte,array length 占4byte,因為里面只有一個(gè)int 類(lèi)型的1,所以數組對象的實(shí)例數據占據4byte,剩余對齊填充占據4byte。
經(jīng)過(guò)以上的內容我們了解了對象在內存中的布局,了解對象的內存布局和對象頭的概念,特別是對象頭的Mark Word的內容,在我們后續分析 synchronize 鎖優(yōu)化 和 JVM 垃圾回收年齡代的時(shí)候會(huì )有很大作用。
JVM中大家是否還記得對象在Suvivor中每熬過(guò)一次MinorGC,年齡就增加1,當它的年齡增加到一定程度后就會(huì )被晉升到老年代中,這個(gè)次數默認是15歲,有想過(guò)為什么是15嗎?在Mark Word中可以發(fā)現標記對象分代年齡的分配的空間是4bit,而4bit能表示的最大數就是2^4-1 = 15。
以上就是詳解Java對象的內存布局的詳細內容,更多關(guān)于Java對象內存布局的資料請關(guān)注腳本之家其它相關(guān)文章!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。
Copyright ? 2009-2022 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 特網(wǎng)科技 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 百度云 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站