1. 程式人生 > 其它 >二、HotSpot 虛擬機器物件探祕

二、HotSpot 虛擬機器物件探祕

二、HotSpot 虛擬機器物件探祕

1. 物件的建立

在語言層面上,建立物件通常是一個 new 關鍵字。

在虛擬機器中,當遇到一條位元組碼 new 指令時,首先檢查該指令的引數能否在常量值中定位到一個類的符號引用,並且檢查該符號引用代表的類是否已被載入、解析和初始化。若沒有,必須先執行響應的類載入過程。

類載入檢查通過後,虛擬機器將為新生物件分配記憶體。物件所需的大小在類載入完成後就能確定下來。

為物件分配空間 等同於 把一塊確定大小的記憶體塊從 Java 堆中劃分出來。

兩種分配方式:

  • 假設 Java堆中記憶體是絕對規整的,所有被使用的放在一邊,空閒的放在另一邊,中間放一個指標作為分界點的指示器。分配記憶體就是移動指標與物件大小相等的距離,這種方式稱為 “指標碰撞

    ”(Bump The Pointer)。

  • 假設 Java堆中記憶體不規整,已使用的記憶體和空閒的相互交錯在一起,此時虛擬機器必須維護一個列表,記錄哪些記憶體是可用的。在分配時就從列表中找到一塊足夠大的空間劃分給物件例項,並更新列表上的記錄。這種分配方式稱為 “空閒列表”(Free List)。

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

存在的問題:在併發情況下,修改指標指向的位置的過程是不安全的

----有兩種可選的解決方案:

  • 對分配記憶體空間的動作進行同步處理 -- 虛擬機器採用 CAS 配上失敗重試的方式保證更新操作的原子性。
  • 把記憶體分配的動作按照執行緒劃分在不同的空間之中進行。即每個執行緒在 Java 堆中預先分配一小塊記憶體,稱為 “本地執行緒分配緩衝”;哪個執行緒要分配記憶體,就在哪個執行緒的本地緩衝區中分配,只有本地緩衝區用完了,分配新的快取區時才需要同步鎖定。

記憶體分配完成後,虛擬機器必須將分配到的記憶體空間都初始化為零值,保證物件的例項欄位在 Java 程式碼中可以不賦初值就直接使用,使程式能訪問到這些欄位的資料型別所對應的零值。

接下來,虛擬機器還要對物件進行必要的設定,都放在物件頭中。上述工作完成後,從虛擬機器的角度來看物件的建立已經完成;但對 Java 程式來說,物件的建立才剛剛開始 -- 建構函式,即 Class 檔案中的 <init>() 方法還沒有執行。

一般來說,new 指令之後會接著執行 <init>() 方法,按照程式設計師的意願對物件進行初始化,這樣一個真正可用的物件才算完全被構造出來。

2.物件的記憶體佈局

物件在堆記憶體中的儲存佈局可以劃分成三部分:物件頭(Header)、例項資料(Instance Data)和 對齊填充(Padding)

物件頭部分包括兩類資訊:

​ 第一類是用於儲存物件自身的執行時資料,這部分資料的長度在 32 位和 64 位的虛擬機器中分別為 32 bit 和 64 bit,官方稱之為 “Mark Word”。物件頭裡的資訊是與物件自身定義的資料無關的額外儲存成本,考慮到虛擬機器的空間效率, Mark Word 被設計為一個有著動態定義的資料結構,以便在極小的空間記憶體儲儘量多的資料,根據物件的狀態複用自己的儲存空間。

​ 第二類是型別指標,即物件指向它的型別元資料的指標,虛擬機器通過這個指標來確定該物件是哪個類的例項。查詢物件的元資料資訊並不一定要經過物件本身。如果物件是一個 Java 陣列,在物件頭中必須要有一塊用於記錄陣列長度的資料。

物件頭正好是 8 位元組的整數倍(1 倍 或者 2 倍)

例項資料部分是物件真正儲存的有效資訊,即在程式程式碼中定義的各種型別的欄位內容。無論是從父類中繼承下來的,還是在子類中定義的欄位都必須記錄下來。本部分的儲存順序會受到虛擬機器引數的影響,相同寬度的欄位總是被分配到一起存放,在父類中定義的變數會出現在子類之前。

對齊填充部分,它並不是必然存在的,僅僅起著佔位符的作用。由於 HotSpot 虛擬機器的自動記憶體管理系統要求物件的起始地址必須是 8 位元組的整數倍,即任何物件的大小必須是 8 位元組的整數倍。

3.物件的訪問定位

Java 程式通過棧上的 reference 資料來操作堆上的具體物件,物件訪問方式是由虛擬機器的實現而定的。主流的訪問方式主要有 使用控制代碼直接指標 兩種。

  • 使用控制代碼:Java 堆中將可能會劃分出一塊記憶體來作為控制代碼池,reference 中儲存的資料是物件的控制代碼地址。控制代碼中包含了物件例項資料與型別資料的地址資訊。

    ​ 優點:在物件被移動時只會改變控制代碼中的例項資料指標,而 reference 本身不需要被修改。

  • 使用直接指標:Java 堆中物件的記憶體佈局就必須考慮如何放置訪問型別資料的相關資訊,reference 中儲存的直接就是物件地址

    ​ 優點:訪問速度快,節省了一次指標定位的時間開銷

HotSpot 虛擬機器使用直接指標訪問物件