[JVM]物件建立過程

Duancf發表於2024-09-16

Java 物件的建立過程

Java物件建立的過程主要分為五個步驟,下面我將詳細介紹這五個步驟。

Step1:類載入檢查

虛擬機器遇到一條new指令時,首先會去檢查這個指令的引數是否能在常量池中定位到這個類的符號引用,並且會檢查這個符號引用所指向的類是否已經完成載入、連線和初始化,如果沒有,必須先執行相應類的類載入過程。

Step2:分配記憶體

當類載入檢查透過後,虛擬機器會為新生物件分配記憶體空間,物件所需記憶體空間的大小在類載入完成後就已經確定了。為新生物件分配記憶體空間其實就是在Java堆中劃分出一塊確定大小的記憶體分配給新生物件。

分配記憶體的方式有“指標碰撞”和“空閒列表”兩種,選擇哪種分配方式取決於Java堆記憶體是否規整。

記憶體分配的兩種方式

指標碰撞

使用場合:堆記憶體規整(即沒有記憶體碎片)的情況下。
實現原理:將用過的記憶體都整合到一邊,沒有用過的記憶體放到另一邊,中間有一個分界指標,當需要為新物件分配記憶體空間時,只需要將分界指標向沒有用過的記憶體一側移動物件記憶體大小位置即可。

空閒列表

使用場合:堆記憶體不規整的情況下。
實現原理:虛擬機器會維護一個列表,該列表記錄了那些記憶體是可用的,當需要為新物件分配記憶體空間時,只需要在列表中找一塊足夠大小的記憶體分配給物件例項,然後更新列表記錄。

選擇以上兩種方式中的哪一種,取決於 Java 堆記憶體是否規整。而 Java 堆記憶體是否規整,取決於 GC 收集器垃圾採用的垃圾收集演算法,垃圾收集相關內容我會在後續文章詳細介紹。

Step3:初始化零值

記憶體分配完成後,虛擬機器需要將新分配的記憶體空間都初始化為零值(不包括物件頭),這一步操作保證了物件的例項欄位可以在Java程式碼中可以不賦初始值就直接使用,程式能夠訪問這些例項欄位的資料型別所對應的零值。

Step4:設定物件頭

初始化零值之後,虛擬機器需要對物件頭進行必要的設定,例如這個物件是哪個類的例項,如何才能找到這個類的後設資料資訊,物件的雜湊碼,物件的GC分代年齡等資訊,這些資訊會存放到物件頭中。另外,根據虛擬機器當前執行狀態的不同,如是否啟用偏向鎖等,物件頭會有不同的設定方式。

Step5:執行init方法

執行完上面四個步驟後,從虛擬機器的角度來看,一個新物件已經產生了,但是從Java程式的角度來看,物件的建立才剛剛開始,init()方法還沒有,所有的欄位都還是零值,所以,一般來說,執行完new指令後會接著執行init方法,將物件按照程式設計師的需求來進行初始化,這樣一個真正可用的物件才算完全產生出來。

物件的記憶體佈局

在 Hotspot 虛擬機器中,物件在堆記憶體中的佈局可以分為 3 塊區域:物件頭(Object Header)、例項資料(Instance Data)和對齊填充(Padding)。

物件頭

物件頭由兩部分組成:物件標記Mark Word和類元資訊(又叫型別指標)組成。

物件標記

物件標記用於儲存物件自身的執行時資料,例如雜湊碼、GC 分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒ID、偏向時間戳等資訊,這些資訊都是與物件自身定義無關的資料,所以Mark Word被設計成一個非固定的資料結構以便在極小的空間記憶體儲存儘量多的資料。它會根據物件的狀態複用自己的儲存空間,也就是說在執行期間Mark Word裡儲存的資料會隨著鎖標誌位的變化而變化。

型別指標

型別指標是物件指向它的類的後設資料的指標,虛擬機器透過這個指標來確定這個物件是哪個類的例項。 並不是所有的虛擬機器實現都必須在物件資料上保留型別指標,換句話說查詢物件的後設資料資訊並不一定要經過物件本身。另外,如果物件是一個Java陣列,那在物件頭中還必須有一塊用於記錄陣列長度的資料,因為虛擬機器可以透過普通Java物件的後設資料資訊確定Java物件的大小,但是從陣列的後設資料中無法確定陣列的大小。

例項資料

例項資料部分是物件真正儲存的有效資訊,也是在程式中所定義的各種型別的欄位內容,包括從父類繼承下來的和本身擁有的欄位。

對齊填充

對齊填充部分不是必然存在的,也沒有什麼特別的含義,僅僅起佔位作用。 因為 Hotspot 虛擬機器的自動記憶體管理系統要求物件起始地址必須是 8 位元組的整數倍,換句話說就是物件的大小必須是 8 位元組的整數倍。而物件頭部分正好是 8 位元組的倍數(1 倍或 2 倍),因此,當物件例項資料部分沒有對齊時,就需要透過對齊填充來補全。

物件的訪問定位

建立物件就是為了使用物件,我們的 Java 程式透過棧上的 reference 資料來操作堆上的具體物件。物件的訪問方式由虛擬機器實現而定,目前主流的訪問方式有:使用控制代碼、直接指標。

控制代碼

如果使用控制代碼的話,那麼 Java 堆中將會劃分出一塊記憶體來作為控制代碼池,reference 中儲存的就是物件的控制代碼地址,而控制代碼中包含了物件例項資料與物件型別資料各自的具體地址資訊。
image

直接指標

如果使用直接指標訪問,reference 中儲存的直接就是物件的地址。
image

兩種訪問方式比較

這兩種物件訪問方式各有優勢,使用控制代碼來訪問的最大好處是 reference 中儲存的是穩定的控制代碼地址,在物件被移動時只會改變控制代碼中的例項資料指標,而 reference 本身不需要修改;使用直接指標訪問方式最大的好處就是速度快,它節省了一次指標定位的時間開銷,HotSpot 虛擬機器主要使用的就是這種方式來進行物件訪問。

相關文章