Java虛擬機(3)對象創建、內存區域及訪問定位

1. 對象的創建

當Java虛擬機遇到一條字節碼new指令時,就會開始虛擬機中對象的創建:

1.1 類加載檢查

  1. 檢查new指令的參數是否能在常量池中定位到一個類的符號引用

  2. 檢查這個符號引用代表的類是否已被加載、解析和初始化過;

如果沒有,那必須先執行相應的類加載過程。

1.2 爲對象分配內存

對象所需內存的大小在類加載完成後便可完全確定,等同於把一塊確定大小的內存塊從Java堆中劃分出來。

選擇哪種分配方式由Java堆是否規整決定,Java堆是否規整又由所採用的垃圾收集器是否帶有空間壓縮整理(Compact)的能力決定。

  • 規整

    即所有被使用過的內存都被放在一邊,空閒的內存被放在另一邊;

  • 不規整

    已被使用的內存和空閒的內存相互交錯在一起。

分配方式:

  1. 指針碰撞 - Bump The Pointer(規整)

    已使用內存在一邊,未使用內存在另一邊,中間放一個作爲分界點的指示器;

  2. 空閒列表 - Free List(不規整)

    虛擬機維護一個列表,記錄上哪些內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄。

注意:對象創建在虛擬機中是非常頻繁的操作,即使僅僅修改一個指針所指向的位置,在併發情況下也會引起線程不安全。

線程安全問題解決:

  1. 對分配內存空間的動作進行同步處理

    採用CAS配上失敗重試的方式保證更新操作的原子性;

  2. 本地線程分配緩衝(Thread Local Allocation Buffer,TLAB)

    把內存分配的動作按照線程劃分在不同的空間之中進行,每個線程在Java堆中預先分配一小塊內存,哪個線程要分配內存,就在哪個線程的本地緩衝區中分配,只有本地緩衝區用完了,分配新的緩存區時才需要同步鎖定。虛擬機是否使用TLAB,可以通過-XX:+/-UseTLAB參數來設定。

1.3 初始化零值

內存分配完成之後,虛擬機必須將分配到的內存空間(但不包括對象頭)都初始化爲零值。

如果使用了TLAB的話,這一項工作也可以提前至TLAB分配時順便進行。

保證了對象的實例字段在Java代碼中可以不賦初始值就直接使用,使程序能訪問到這些字段的數據類型所對應的零值。

1.4 對象頭設置

Java虛擬機還要對對象進行必要的設置,存放在對象的對象頭(Object Header)之中:

對象是哪個類的實例、如何找到類的元數據信息、對象的哈希碼(實際上對象的哈希碼會延後到真正調用Object::hashCode()方法時才計算)、對象的GC分代年齡等。

1.5 按意圖初始化

對對象進行必要設置後,從虛擬機的視角來看,一個新的對象已經產生了。Java 程序開發來說,對象創建纔剛開始,需要進行一些初始化操作。new指令之後會接着執行()方法,按照程序員的意願對對象進行初始化,這樣一個真正可用的對象纔算完全被構造出來。

總結

2.對象的內存佈局

HotSpot虛擬機裏,對象在堆內存中的存儲佈局可以劃分爲三個部分:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)。

2.1 對象頭

對象頭主要包含兩部分:

  • 用於存儲對象自身的運行時數據(Mark Word);

    如哈希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等;

    被設計成一個有着動態定義的數據結構,以便在極小的空間內存儲儘量多的數據,根據對象的狀態複用自己的存儲空間。

  • 類型指針,即對象指向它的類型元數據的指針。

    通過這個指針來確定該對象是哪個類的實例,但並不是所有的虛擬機實現都必須在對象數據上保留類型指針。

    如果對象是一個Java數組,那在對象頭中還必須有一塊用於記錄數組長度的數據,因爲虛擬機可以通過普通Java對象的元數據信息確定Java對象的大小,但是如果數組的長度是不確定的,將無法通過元數據中的信息推斷出數組的大小。

2.2 實例數據

對象真正存儲的有效信息,即我們在程序代碼裏面所定義的各種類型的字段內容。

無論是從父類繼承下來的,還是在子類中定義的字段都必須記錄起來。

這部分的存儲順序會受到虛擬機分配策略參數(-XX:FieldsAllocationStyle參數)和字段在Java源碼中定義順序的影響。

2.3 對齊填充

佔位符作用。

由於HotSpot虛擬機的自動內存管理系統要求對象起始地址必須是8字節的整數倍,換句話說就是任何對象的大小都必須是8字節的整數倍。對象頭部分已經被精心設計成正好是8字節的倍數(1倍或者2倍),因此,如果對象實例數據部分沒有對齊的話,就需要通過對齊填充來補全。

總結

3. 對象的訪問定位

創建對象後續如何使用該對象?

我們的Java程序會通過棧上的reference數據來操作堆上的具體對象。

由於reference類型在《Java虛擬機規範》裏面只規定了它是一個指向對象的引用,並沒有定義這個引用應該通過什麼方式去定位、訪問到堆中對象的具體位置,所以對象訪問方式也是由虛擬機實現而定的,主流的訪問方式主要有使用句柄和直接指針兩種。

3.1 句柄

Java堆中將可能會劃分出一塊內存來作爲句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自具體的地址信息

3.2 直接指針

reference中存儲的直接就是對象地址。

總結

本文主要介紹了JVM對象創建、對象內存佈局、對象訪問定位,接下來會進一步閱讀《深入理解Java虛擬機》,並進行更多內容的講解、總結。

歡迎點贊/評論,你們的贊同和鼓勵是我寫作的最大動力!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章