1. 程式人生 > >第二篇 java虛擬機器物件探祕

第二篇 java虛擬機器物件探祕

  上章介紹完java虛擬機器的執行時資料區之後,我們大概知道了虛擬機器記憶體的情況,下面我們講解虛擬機器記憶體中的其他細節,比如他們是如何建立,如何佈局,如何訪問的。我們以java堆為例,深入探討Hotspot虛擬機器在java堆中物件的分配,佈局以及訪問的全過程。

2.1 物件的建立

  java程式執行過程中無時無刻都有物件被創建出來。在語言層面,建立物件(克隆,反序列化)通常僅僅是一個new關鍵字,而在虛擬機器中,物件(僅包括不同物件,不包括陣列和class物件)的建立,首先將去檢查new指令的引數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已經被載入,解析,和初始化。如果沒有那麼先執行相應類的載入過程 。

2.1 .1 物件的分配記憶體空間機制

  在類載入檢查通過之後,接下來虛擬機器將為新生物件分配記憶體。物件所需記憶體的大小在類載入完成後便可完全確定,為物件分配記憶體空間的任務等同於把一塊確定大小的記憶體從java堆中劃分出來。假設java堆中記憶體是絕對規整的,所有用過的記憶體都放在一邊,空閒的記憶體就放在另一邊,中間放著一個指標作為分界點的指示器,那所分配的記憶體就僅僅是把指標向空閒空間挪動一段與物件大小相等的距離,這種分配方式稱為 “指標碰撞”。如果java堆中記憶體並不是規整的,已使用的記憶體和空閒的記憶體相互交錯,那就沒有辦法簡單的進行指標碰撞了,虛擬機器就必須維護一個列表,記錄那些記憶體塊時可用的,在分配記憶體的時候從列表中找到一塊足夠大的空間劃分給物件例項,並更新列表上的記錄,這種分配方式成為“空閒列表”。選擇哪種分配方式由java堆是否規整決定,而java堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定。因此,在使用Serial,ParNew等帶有compact過程的收集器時,系統採用的分配演算法是指標碰撞,而使用cms這種基於Mark-sweep演算法的收集器時,通常採用空閒列表。
   除如何劃分可用空間之外,還有另外一個需要考慮的問題時建立物件在虛擬機器中是非常頻繁的行為,即使是僅僅修改一個指標所指向的位置,在併發情況下也並不是執行緒安全的,可能出現正在給物件a分配記憶體,指標還沒來及修改,物件b又同時使用了原來的指標分配記憶體。解決這種問題有兩種解決方案:一種是對分配記憶體空間的動作進行同步處理—-實際上虛擬機器採用CAS配上失敗重試的方式保證了更新操作的原子性;另一種是把記憶體的分配動作按照執行緒劃分在不同空間進行,即每個執行緒在java堆中都預先分配一塊小記憶體,稱為本地執行緒分配緩衝(TLAB)。哪個執行緒要分配記憶體,就在那個執行緒的TLAB上分配,只有TLAB用完並分配新的tlab時,才需要同步鎖定。虛擬機器是否使用TLAB,可以通過-XX:+/-UserTLAB引數來設定。
   記憶體分配完成後,虛擬機器需要將分配到的記憶體空間賦值0,這一步保證了物件的例項欄位即使在java程式碼中沒有初始值,也能夠直接使用,程式能夠訪問到這些欄位的資料型別所對應的初值。
   接下來,虛擬機器要對物件進行必要的設定,比如:物件是哪個類的例項,如何才能找到類的元資料資訊,物件的hash碼,物件的GC分代年齡資訊,這些資訊存放在物件的物件頭之中(object header),根據虛擬機器當前執行狀態的不同(是否啟用偏向鎖),物件頭會有不同的設定方式。
   從虛擬機器角度來說,一個新的物件產生了,但從java程式的角度來看,物件的建立才剛開始,init方法還沒有執行,所有的欄位還都為零,所以,一般來說(由位元組碼中是否跟隨invokespecial指令所決定),執行new指令之後會接著執行init方法。把物件按照程式設計師的意願進行初始化,這樣一個真正可用的物件才算完全產生出來。