JVM記憶體模型(二)—— HotSpot虛擬機器分析
上一節我們講了Java虛擬機器的理論記憶體模型,同時我們也提到了,這些只是Java虛擬機器規範中的內容,如果我們要研究一個物件是如何建立、如何佈局等一系列細節問題的時候,我們就必須在具體的虛擬機器中分析,因為不同的虛擬機器的實現是不一樣的,下面我們就選最常用、最普遍的虛擬機器——HotSpot來介紹。
物件的建立
Java是一門面向物件語言,在建立物件的時候僅適用關鍵字new即可創造一個物件。接下來我們就說一下使用new關鍵字之後,記憶體中究竟發生了什麼變化。
虛擬機器讀到new指令之後,首先檢查這個指令的引數是否能在常量池中定位到一個類的符號引用,並且檢查這個類是否執行過類載入過程,如果沒有載入過,則先執行類載入。
類載入檢查完成後,虛擬機器將會為新物件分配記憶體。物件所需的記憶體大小在類載入完成之後便可以確定。下邊的任務便是Java堆中劃分出一塊規整的記憶體。但是Java堆採用的垃圾收集演算法的不同,給新物件分配記憶體的方式也不同:
- 當垃圾收集演算法使用複製演算法、標記-整理演算法時,Java堆中的記憶體是絕對規整的,即所有被使用的記憶體放在一側,未被使用的記憶體放在另一側,中間放著一個指標作為分界點的指示器,分配記憶體僅僅是把指標向空閒空間挪動一個新物件大小的距離。這種方式稱為“指標碰撞”。
- 當垃圾收集演算法使用標記-清楚演算法時,Java堆中的記憶體不是規整的。空閒記憶體與非空閒記憶體相互交錯。這時虛擬機器維護一個記憶體列表,記錄哪些記憶體塊是可用的,再分配記憶體的時候從列表中找到一塊足夠大的空間劃分給新物件,並更新列表上的記錄。這種分配方式稱為“空閒列表”。
在為新物件分配空間的時候還需要考慮一個問題,因為虛擬機器中物件的建立時十分頻繁的,如果使用指標碰撞法,那麼必須要考慮到在併發的情況下是否執行緒安全的問題。虛擬機器給出的解決方案有兩個:
- 對分配記憶體空間的動作進行同步處理,在虛擬機器實際實現中採用CAS配上失敗重試的方式保證操作更新的原子性。
- 把記憶體的分配按照執行緒劃分在不同空間之中進行,這樣每個執行緒預先在Java堆中分配一小塊記憶體,稱為本地執行緒分配緩衝(Thread Local Allocation Buffer,TLAB)。哪個執行緒要分配記憶體,就在哪個執行緒的TLAB上進行分配,只有TLAB用完需要分配新的TLAB的時候,才需要同步鎖定。虛擬機器是否使用TLAB,可以通過-XX:+/-UseTLAB引數來設定。
記憶體分配完成之後,虛擬機器需要將分配到的記憶體空間初始化為零值(物件頭除外,關於物件記憶體區域的組成在物件記憶體佈局中還會講到),如果使用TLAB,這一過程也可以在TLAB分配時進行。緊接著對物件頭進行設定。到這一步的時候,從虛擬機器的角度來說,一個新物件已經產生了,但是從程式或者程式設計師角度來看,物件才剛剛建立,此時還沒有執行物件的構造方法,所有的欄位還都是零。一般來說這一步之後緊接著就會執行構造方法,將物件按照程式設計師的意願初始化,這樣才是一個真正可用的物件。
物件記憶體的佈局
先來看一張圖:
我們可以很直觀的看出來,物件儲存分為三塊區域:物件頭、例項資料和對齊填充。
物件頭
物件頭有兩部分資訊:
第一部分用於儲存物件自身的執行時資料,比如雜湊碼、GC分代年齡(用於垃圾回收)、鎖狀態標誌、執行緒持有的鎖、偏向執行緒ID、偏向時間戳等。這部分資料的長度取決於虛擬機器的位數,在32位和64位虛擬機器中長度分別為32bit和64bit。
第二部分用於儲存型別指標,也就是物件指向它的類元資料的指標。虛擬機器通過這個指標確定物件是哪個類的例項。但是並不是所有的虛擬機器實現都必須有型別指標,這取決於引用型別訪問物件的方式。目前有使用控制代碼和直接指標兩種方式來訪問物件:
- 如果使用控制代碼訪問的話,那麼Java堆中將會劃分出一塊記憶體來作為控制代碼池,引用中儲存的就是物件的控制代碼地址,控制代碼中包含了物件例項資料與型別資料各自的具體地址資訊,如圖所示:
- 如果使用直接指標訪問,那麼Java堆物件的佈局中就必須考慮如何防止型別資料的相關資訊,而引用中儲存的直接就是物件地址,如圖所示:
這兩種訪問方法各有優勢,使用控制代碼來訪問物件最大的好處就是引用中儲存的資料比較穩定,因為如果發生垃圾回收,變化的只是控制代碼中指向物件例項資料的指標,引用的值本身不用修改。使用直接指標最大的好處就是,速度比較快,省去了一次指標定位的時間開銷。由於物件的訪問在Java中十分頻繁,所以也是一項非常可觀的開銷節約。HotSpot虛擬機器使用直接指標的方式訪問物件。
例項資料
例項資料部分是物件真正儲存的有效資訊,也是程式程式碼中所定義的各種型別的欄位內容。無論是從父類繼承下來的,還是子類中定義的,都需要記錄起來。欄位的儲存順序會受到虛擬機器分配策略引數和欄位在Java原始碼中定義順序的影響。HotSpot虛擬機器預設的分配策略為longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers)。從分配策略中可以看出相同寬度的欄位總是分配到一起。在滿足這個前提條件的情況下,在父類中定義的變數會出現在子類之前,但是如果設定CompactFields引數值為true(預設為true),那麼子類中較窄的變數也可能會插入到父類的空隙中去。
對齊填充
對其填充不是必然存在的,僅起到佔位符的作用。因為HotSpot虛擬機器規定自動記憶體管理系統要求物件其實地址必須是8位元組的整數倍,換句話說,就是物件的大小是8位元組的整數倍,如果不滿足條件,則需要對齊填充來補全。
結語
到這裡Java記憶體模型關於HotSpot虛擬機器的例項分析就完成了,這一部分內容還是很多的,仔細的、深入的研究之後才能更好的認識虛擬機器,使用Java這門語言。 與君共勉。
如有錯誤,歡迎指摘。