1.對象在堆內存中佈局
2.對象的對象頭
3.對象的實例數據
4.對象的對齊填充
5.看看Object的對象頭
6.看看自定義對象的對象頭
7.總結
1.對象在堆內存中佈局
當我們寫入這樣一行代碼
Object object = new Object();
的時候,我們都知道它會在我們的JVM堆->新生區->伊甸園區新建一個對象,但是我們可能只是知道這個對象在哪兒,但是對這個對象的內存結構卻知之甚少,今天我們就來細説一下,JAVA對象的內存佈局。
我們先來看這樣一張圖:
java對象在內存中的佈局分為以下三個部分:
1)對象頭
2)實例數據
3)對齊填充
接下來我們來詳細介紹以下這三個部分。
2.對象的對象頭
首先是對象頭,這是java對象內存佈局中,最重要的一個部分,它分為了兩個部分:
1)對象標記Mark Word
2)類型指針(類元信息)
首先是對象標記:
默認存儲的是HashCode,分代年齡,鎖標誌位,GC標記等信息。
這些信息是與對象自身定義無關的數據,所以MarkWork被設計成一個非固定的數據結構,以便在小的空間內能夠儘量存儲更多的數據。
它會根據對象的狀態複用自己的存儲空間,也就是説在運行期間,MarkWork裏的存儲對象會隨着鎖標誌位的改變而發生變化。
類型指針(類元信息):
對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。
對象頭的大小:
在64位系統中,Mark Word佔了8個字節,類型指針佔了8個字節,一共是16個字節。
3.對象的實例數據
這個部分應該是我們每天接觸的部分,我們平時寫的字段都在這裏,還包括父類的屬性信息(如果是數組的實例部分還包括數組的長度),這部分內存按照4個字節對齊。
4.對象的對齊填充
虛擬機要求對象的起始地址必須是8字節的整數倍,填充數據不是必須存在的,如果對象頭+對象的實例數據不是8的整數倍,那麼對象的對齊填充就會按照8字節補充對齊。
5.看看Object的對象頭
在使用java查看Object的對象頭之前,我們要先引入一個工具類,這樣才可以更方便地查看對象頭。
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
接着我們小試一下,看看我們平時使用的Object類的對象頭有幾個字節:
public static void main(String[] args) {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
發現這裏的問題沒有,前面我們説,對象頭佔16個字節,但是它這裏只佔12個字節?最後一排顯示了丟失?所以對象頭究竟是佔16個字節還是12個字節呢?
答案是————都對。 因為JVM默認幫我們開啓了對象頭的壓縮,打開這個壓縮,對象頭就佔12位,關閉這個壓縮,對象頭就佔16位,我們再來試一下:
我們先在JVM運行的時候,使用這個參數:-XX:-UseCompressedClassPointers 這個參數就是關閉壓縮參數。
然再運行,獲得的結果為:
6.看看自定義對象的對象頭
Object的對象頭看完了,我們看看自定義的對象頭又有什麼區別。
我們先自定義一個對象
class MyObj{
private Integer id;
private String name;
private Integer age;
private String address;
private Integer status;
private Date createTime;
private Date updateTime;
}
我們發現,對象實例數據區域明顯多了很多自定義的字段,並且進行了對齊填充。
7.總結
今天我們學習了JAVA對象在內存中的佈局,這一部分內容是為了為講解鎖升級做鋪墊。