1. 程式人生 > 其它 >《深入理解Java虛擬機器》——HotSpot虛擬機器物件

《深入理解Java虛擬機器》——HotSpot虛擬機器物件

2.3 HotSpot虛擬機器物件

HotSpot虛擬機器在Java堆中物件分配、佈局和訪問得全過程。

2.3.1 物件的建立

流程為:類載入檢查->為物件分配記憶體->初始化零值->設定物件頭->執行Class中方法

String str = new String("str");
-> 位元組碼
LINENUMBER 13 L0
    NEW java/lang/String
    DUP
    LDC "str"
    INVOKESPECIAL java/lang/String.<init> (Ljava/lang/String;)V
    ASTORE 1
  1. 類載入檢查
    Java虛擬機器處理new指令時,首先檢查該指令的引數能否能在常量池中定位到類的符號引用,並且檢查該引用代表的類是否已被載入、解析和初始化過。如果沒有,則先執行相應的類載入過程。
  2. 分配記憶體
    在類載入檢查通過後,接下來虛擬機器將為新生物件分配記憶體。物件所需物件記憶體大小在類載入檢查完成後便可確定(從Java堆中劃分一塊確定大小得記憶體快)。分配記憶體的方式有“指標碰撞”和“空閒列表”兩種。

分配記憶體方式詳解:
指標碰撞:如果Java堆中記憶體是絕對規整的,所有被使用過的記憶體都被放在一邊,空閒的記憶體放在另一邊,中間放著一個指標作為分界點的指示器,通過把指標像空閒空間方向挪動一段與物件大小相等的距離來分配記憶體。當使用Serial、ParNew等帶壓縮整理過程的器時,系統採用的分配演算法時指標碰撞。
空閒列表:如果Java堆中的記憶體並不是規整的,已被使用的記憶體和空閒的記憶體交錯在一起,沒法使用“指標碰撞”分配記憶體。此時虛擬機器必須維護一個空閒記憶體的列表,來記錄堆中那些記憶體是可用的,在分配記憶體的時候在列表中找到一塊足夠大的空間劃分給物件例項,並更新空閒列表上的記錄。當時用CMS這種基於清除演算法的收集器時,理論上(強調“理論上”是因為CMS的實現裡面,設計了一個叫作Linear Allocation Buffer的分配緩衝區,通過空閒列表拿到一大塊分配緩衝區之後,在它裡面仍然可以使用指標碰撞來分配記憶體)只能採用“空閒列表”的方式來分配記憶體。

分配記憶體保證執行緒安全的方案
一種是分配記憶體空間的動作進行同步處理——實際上是虛擬機器採用CAS配上失敗重試的方式保證更新操作的原子性;
另一種是採用本地執行緒分配緩衝(TLAB),每個執行緒在Java堆中預先分配一小塊記憶體來給新生物件分配使用,當本地緩衝區用完了,分配新的快取區時需要同步鎖定。可以通過-XX:+/-UseTLAB引數設定虛擬機器是/否使用TLAB。

  1. 初始化零值
    虛擬機器將分配到的記憶體空間都初始化零值,保證物件例項欄位在Java程式碼中可以不賦初始值直接使用,使程式能訪問到這些欄位的資料型別所對應的零值。如果使用TLAB,此工作會提前至TLAB分配時順便進行。

  2. 物件頭設定
    對物件進行必要的設定——類的元資料資訊、物件的雜湊碼、物件的GC分代年齡等資訊,放置在物件頭中。

  3. 執行Class檔案中()方法構造
    new指令之後,從虛擬機器的視角看一個新的物件已經產生,new指令之後會接著執行()方法,對物件進行初始化,一個真正可用的物件才算完全被構造出來。

2.3.2 物件的記憶體佈局

在HotSpot虛擬機器裡,物件在堆記憶體中的儲存佈局可以劃分為三個部分:物件頭、例項資料和對齊填充。

  1. 物件頭包括兩類資訊:Mark Word物件頭資料和Klass Pointer型別指標。
  • Mark Word用於儲存物件自身的執行時資料,如雜湊碼(HashCode)、GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒ID、偏向時間戳等。根據虛擬機器的位數確定資料長度;根據物件的狀態複用儲存空間,動態定義資料結構,提高空間利用效率來儲存更多的資料。
儲存內容 標誌位 狀態
物件雜湊碼、物件分代年齡 01 未鎖定
指向鎖記錄的指標 00 輕量級鎖定
指向重量級鎖的指標 10 膨脹(重量級鎖定)
空,不需要記錄資訊 11 GC標記
偏向執行緒ID、偏向時間戳、物件分代年齡 01 可偏向

32位虛擬機器中未鎖定(物件狀態標誌位為01)物件的空間分配

Mark Word的資料結構

  • Klass Pointer用於儲存物件指向它型別元資料的指標,Java虛擬機器通過這個指標確定物件的例項型別。如果物件是陣列型別,則需要三個機器碼,因為JVM虛擬機器可以通過Java物件的元資料資訊確定Java物件的大小,但是無法從陣列的元資料來確認陣列的大小,所以用一塊來記錄陣列長度。
  1. 例項資料部分是物件真正儲存的有效資訊。

  2. 對齊填充並不是必然存在的,也沒有特別的含義,它僅僅起著佔位符的作用。由於HotSpot VM的自動記憶體管理系統要求物件起始地址必須是8位元組的整數倍,換句話說就是物件的大小必須是8位元組的整數倍。物件頭正好是8位元組的倍數(1倍或者2倍),因此當物件例項資料部分沒有對齊的話,就需要通過對齊填充來補全。

2.3.3 物件的訪問定位

主流的訪問方式主要有使用控制代碼和直接指標兩種。

  • 控制代碼訪問:Java堆中將可能會劃分出一塊記憶體來作為控制代碼池,reference中儲存的就死物件的控制代碼地址,而控制代碼中包含了物件例項資料與型別資料各自具體的地址資訊,結構如下圖所示。
  • 直接指標:Java堆中物件的記憶體佈局就必須考慮如何放置訪問型別資料的相關資訊,reference中儲存的直接就是物件地址,如果只是訪問物件本身的話,就不需要多一次間接訪問的開銷,結構如下圖所示。

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