深入理解java虛擬機——HotSpot虛擬機對象

1.對象的創建

①虛擬機遇到一條new指令是,首先將去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已被加載、解析和初始化過。如果沒有必須先加載類。(後續再補充)

②接下來虛擬機將爲新生對象分配內存。對象所需內存的大小在類加載完成後便可完全確定。爲對象分配內存的方式可以分爲在連續內存空間上的的“指針碰撞”方式和在不連續內存空間的“空閒列表”方式。分配對象內存空間時即使僅僅修改指針所指向的位置,在併發情況下也不是線程安全的,兩種解決方案:一種對分配內存空間的動作進行同步處理——實際上虛擬機採用CAS配上失敗重試的方式保證更新操作的原子性;另一種是把內存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在java堆中預先分配一小塊內存,稱爲本地線程分配緩衝(Thread Local Allocation Buffer,TLAB)。

③內存分配完成後,虛擬機需要將分配到的內存空間都初始化爲零值(不包括頭對象)。如果使用TLAB可以提前至分配TLAB時進行

④接下來虛擬機要對對象進行必要的設置,例如對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息。這些信息放在對象頭中。

2.對象的內存佈局

對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)。

HotSpot虛擬機的對象頭包括兩部分信息,第一部分用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等等,這部分數據的長度在32位和64位的虛擬機(暫不考慮開啓壓縮指針的場景)中分別爲32個和64個Bits,官方稱它爲“Mark Word”。對象需要存儲的運行時數據很多,其實已經超出了32、64位Bitmap結構所能記錄的限度,但是對象頭信息是與對象自身定義的數據無關的額外存儲成本,考慮到虛擬機的空間效率,Mark Word被設計成一個非固定的數據結構以便在極小的空間內存儲儘量多的信息,它會根據對象的狀態複用自己的存儲空間。例如在32位的HotSpot虛擬機中對象未被鎖定的狀態下,Mark Word的32個Bits空間中的25Bits用於存儲對象哈希碼(HashCode),4Bits用於存儲對象分代年齡,2Bits用於存儲鎖標誌位,1Bit固定爲0,在其他狀態(輕量級鎖定、重量級鎖定、GC標記、可偏向)下對象的存儲內容如下表所示。

存儲內容

標誌位

狀態

對象哈希碼、對象分代年齡

01

未鎖定

指向鎖記錄的指針

00

輕量級鎖定

指向重量級鎖的指針

10

膨脹(重量級鎖定)

空,不需要記錄信息

11

GC標記

偏向線程ID、偏向時間戳、對象分代年齡

01

可偏向

 

對象頭的另外一部分是類型指針,即是對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。並不是所有的虛擬機實現都必須在對象數據上保留類型指針,換句話說查找對象的元數據信息並不一定要經過對象本身。另外,如果對象是一個Java數組,那在對象頭中還必須有一塊用於記錄數組長度的數據,因爲虛擬機可以通過普通Java對象的元數據信息確定Java對象的大小,但是從數組的元數據中無法確定數組的大小。

3.對象的訪問定位

java程序需要通過棧上的reference數據來操作堆上的具體對象。由於reference類型在java虛擬機規範中只規定了一個指向對象的引用,並沒有定義這個引用用何種方式去定位、訪問堆中的對象的具體位置,所以它取決於虛擬機的實現。目前主要有句柄和直接指針兩種。

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

使用直接指針的方式好處就是速度更快,它節省了一次指針定位的時間開銷,由於對象的訪問在java中非常頻繁,因此優勢也很大。HotSpot使用直接指針的訪問方式。

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