JVM 學習筆記 (一) 對象的創建過程

在Java裏,創建對象只需要new 關鍵字即可。那麼JVM是怎麼去創建對象的呢?

當JVM 遇上new 關鍵字?

JVM 遇上new 指令時

第一步 類加載檢查

首先會去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並且檢查這個類是否已經被加載。如果沒有加載,就去加載類。

簡述一下:

在常量池中定位類的符號引用,檢查類是否被加載初始化 如果沒有被加載就會去加載初始化類

第二步 分配內存空間

類加載檢查通過之後,虛擬機將會爲新生對象分配內存,對象所需內存大小在類加載完成後就已經確定。爲對象分配內存空間的任務等同於把一塊確定大小的內存從java堆中劃分出來。假設Java堆中內存是絕對規整的,所有用過的內存放在一邊,空閒的內存放在另一邊,中間放着一個指針作爲分界點的指示器,那所分配的內存就只是把那個指針向空閒空間那邊挪動一個對象大小的距離,這種分配方式稱爲指針碰撞(Bump the Pointer)。
如果java堆中內存不是規整的,已使用內存和空閒內存相互交錯,那就不能指針碰撞了,虛擬機必須維護一個列表,表上記錄哪些內存塊是可用的,在分配的時候從列表上找到一塊足夠大的空間劃分給對象,並更新表上記錄,這種分配方式稱爲空閒列表(Free List)。選擇哪種分配方式由java堆是否規整決定,而java堆是否規整由所採用的垃圾收集器是否帶有壓縮整理功能決定。

簡述一下:

類加載檢查之後,JVM開始分配內存,分配內存有兩種方式:

  • 1.內存規整時,採用指針碰撞(Bump the Pointer)
    假設 空閒內存在左邊,用過的內存在右邊,中間就是指針作爲分界點。分配內存時,指針就向左邊移動一下。
  • 2.內存不規整時,採用空閒列表(Free List)
    JVM維護一個列表,表上記錄內存塊使用情況,分配內存時就從表上找一塊足夠大的內存空間。

內存是否規整取決於垃圾收集器是否有壓縮整理功能.

創建對象的線程安全性

爲什麼創建對象會有線程安全性問題

可能出現正在給對象A分配內存,指針還沒來得急修改,對象B又同時使用了原來的指針來分配內存。

解決方案
  • 對分配內存空間的動作進行同步處理
    虛擬機採用CAS配上失敗重試的方式保證更新操作的原子性。
  • 本地線程分配緩衝(Thread Local Allocation Buffer,TLAB)
    把內存分配的動作按照線程劃分在不同的空間之中進行,每個線程在Java堆中預先分配一小塊內存,稱爲本地線程分配緩衝。哪個線程需要分配內存,就在哪個線程上的TLAB上分配,只有TLAB用完並分配新的TLAB時,才需要同步鎖定。虛擬機是否使用TLAB,可以通過-XX:+/-UseTLAB 參數設定。(TLAB是Eden中的一個區域)

第三 初始化內存空間

內存分配完成後,虛擬機需要將分配到的內存空間都初始化爲零值(不包括對象頭)。如果使用TLAB,這一過程也可以提前到TLAB分配時進行。
這一操作保證了對象的實例字段在Java代碼中可以不賦初始值就可以直接使用。 程序能訪問到這些字段的數據類型所對應的零值。

簡述一下:

將內存空間初始化爲零值(不包括對象頭)。

第四 設置對象頭

空間初始化完成之後,開始對對象進行必要的設置,例如這個對象是哪個類的實例,如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息。這些信息存放在對象的對象頭(Object Header) 之中。根據虛擬機當前的運行狀態不同,如是否啓用偏向鎖等,對象頭會有不同的設置方式。

簡述一下:

設置對象頭的信息。

第五 執行 init方法

在上面工作都完成之後,從虛擬機的視角來看 ,一個新的對象已經產生了,但從程序的角度來看,對象創建纔剛剛開始----< init > 方法還沒有執行,所有的字段還爲零,所以一般來說(由字節碼中是否跟隨invokespecial指令所決定),執行new指令之後會接着執行 < init > 方法初始化。這樣一個真正可用的對象纔算完全創建完成。

簡述一下:

字節碼中有ininvokespecial指令
執行< init >方法初始化。

參考資料

摘抄學習於深入理解Java虛擬機
介紹TLAB的一篇博客

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