HotSpot虛擬機中對象的分配、佈局和訪問

本篇文章以HotSpot虛擬機和常用的內存區域Java堆爲例,深入探討HotSpot在Java堆中對象分配、佈局和訪問的全過程。

對象的創建

Java對象的創建和初始化一文中,我們知道了創建對象的幾種方式。虛擬機在遇到創建指令時,首先會去檢查對象所代表的類是否已經被加載、解析和初始化過。如果沒有,那麼必須執行相應的類加載過程,詳細細節可參考Java類的加載和初始化

空間分配
在類加載完成後,將爲對象分配空間(把一塊確定大小的內存從堆中劃分出來)。分配方式包括以下兩種:
1、假設Java堆中內存是絕對規律的,所有用過的內存都放在一邊,空閒的內存放在另一邊,中間放着一個指針作爲分界點的指示器,分配內存就僅是把那個指針向空閒空間那邊挪動一段與對象大小相等的距離,這種分配方式稱爲指針碰撞


2、如果堆內存不是規律的,已使用的內存和空閒內存相互交錯,虛擬機就必須維護一個列表,記錄上哪些內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄,這種分配方式稱爲空閒列表

選擇哪種分配方式由Java堆是否規整決定,而堆是否規整又是由所採用的垃圾收集器是否帶有壓縮整理功能決定。因爲,在使用Serial、ParNew等帶Compact過程的收集器時,系統採用的分配算法是指針碰撞,而使用CMS這種基於Mark-Sweep算法的收集器時,通常採用空閒列表。

在分配空間時,還需考慮併發問題。可能出現正在給對象A分配內存,指針還沒來得及修改,對象B又同時使用了原來的指針來分配內存的情況。解決這個問題有兩種方案:
1、對分配空間的動作進行同步處理,實際上虛擬機採用CAS配上失敗重試的方式保證更新操作的原子性。
2、把內存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在Java堆中預先分配一小塊內存,稱爲本地線程分配緩衝(Thread Local Allocation Buffer,TLAB)。

內存分配完成後,虛擬機需要將分配到的內存空間都初始化爲零值。然後對對象進行必要的設置,例如這個對象是那個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息。

對象的內存佈局

在HotSpot虛擬機中,對象在內存中存儲的佈局分爲3塊區域:對象頭實例數據對象填充


對象的訪問

建立對象是爲了使用對象,Java程序需要通過棧上的reference數據來操作堆上的具體對象。reference類型是一個指向對象的引用,並沒有定義這個引用應該通過何種方式去定位、訪問堆中的對象的具體位置,所以對象訪問方式也是取決於虛擬機實現而定的。目前主流的訪問方式有使用句柄和直接指針兩種:
1、使用句柄訪問,Java堆中會劃分出一塊內存來作爲句柄池,reference存儲對象的句柄地址,而句柄中包含了對象實例數據和類型數據各自的具體地址信息。



2、使用直接指針訪問,那麼Java堆對象的佈局中就必須考慮如何放置訪問類型數據的相關信息(對象頭中的類型指針),而reference中存儲的直接就是對象地址。


這兩種對象訪問方式各有優劣,使用句柄的最大好處就是reference中存儲的是穩定的句柄地址,在對象被移動(垃圾收集時移動對象是非常普遍的行爲)時只需改變句柄中的實例數據指針,而reference本身不需要修改。

使用直接指針訪問方式的最大好處就是速度更快,它節省了一次指針定位的時間開銷。就HotSpot虛擬機而言使用的是直接指針方式。

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