05-JVM對象探祕

1、對象的內存佈局

        以Hotspot虛擬機爲例,對象在內存中的結構能夠分爲三部分:對象頭(header)、實例數據(instance data)、對齊填充(padding)。

1.1.對象頭

        對象頭的結構大致類似,但不一樣JVM的具體實現使得它們略有差異。通常來講,對象頭都包含了標記字、類型指針兩部分信息,若是對象是數組,還會額外包含數組長度信息。

1.1.1.標記字

        存儲對象自身的運行時數據(即狀態),包括哈希碼、GC分代年齡、鎖狀態標誌、線程持有鎖、偏向線程id、偏向時間戳等。它們的存儲結構相似於C語言中的「位字段」,官方稱之爲「Mark Word(標記字)」。「標記字」以「字」做爲基本的存儲單元,即在32位虛擬機中,數據長度爲32bit;而在64位虛擬機中,數據長度爲64bit。
        以32bit虛擬機爲例,有固定的2bit用於存儲鎖標誌位,隨着鎖標誌位值不一樣,其餘位存儲的內容與位長度也不一樣。這一點相似於C語言中的聯合結構(union),且聯合的每個成員都是位字段結構。

1.1.2.類型指針

        類型指針即對象指向它的類元數據(class metadata)的指針,虛擬機經過該指針肯定這個對象是哪一個類的實例。 但須要注意的是,並不是全部虛擬機實現中都會在對象頭包含類型指針,也能夠採用其餘方式保留對象的類型信息

1.1.3.數組長度

        在java中,數組也屬於對象,那麼理所固然的須要維護數組長度,該信息存放在對象頭中。
 

1.2.實例數據

        實例數據即對象的字段(或稱爲成員變量)存儲的數據信息,包含了從父類繼承及本身定義的全部字段。且字段在內存中存儲的順序並不等於類中的定義順序,它受到虛擬機策略的影響(主要考慮到內存對齊以及使用率的問題)。
 

1.3.對齊填充

        相似於C中結構體struct的內存對齊,java對象的內存位置也須要對齊。
        咱們經常使用的Hotspot虛擬機要求每一個對象的起始地址爲8字節的整數倍,也就是說,若一個對象結束地址非8字節整數倍,則須要佔位符進行填充以保證對齊。
 

2、對象的訪問定位

        虛擬機規定,須要經過棧上的「reference(引用)」來操做具體對象。對於該規定,目前有兩種主流的實現方式:
  • 經過句柄(handler)實現:該種方式會在堆中劃出一塊「句柄池」內存空間,每一個棧上的引用直接指向句柄池中的句柄,而句柄中又會維護對象指針和類型指針。使用句柄帶來的好處是,棧上的reference存儲穩定的句柄地址,GC形成的對象移動只會致使句柄中相應的指向地址改變,而reference地址不改變。

 

  • 經過直接指針(direct point)實現:即在對象的對象頭中維護類型指針。棧的reference指向對象,而對象頭中的類型指針指向對象類型數據。使用直接指針的好處是,對象的訪問速度快,節省了指針二次尋址的開銷。
 
 

3、對象的建立過程

        對象的建立過程要經歷如下幾個階段:

3.1.類加載

  • 檢查到new指令;
  • 虛擬機檢查在常量池中是否有該類的符號引用,包括該符號引用表明的類是否已被加載、解析、初始化;
  • 沒有,則先加載類(加載過程後續章節會詳細講述);有的話,直接建立對象;

3.2.內存分配

(1)內存分配的方式
         一個對象所需內存在類加載時即可肯定,內存分配方式有兩種:
  • 指針碰撞法:若java堆中內存是絕對規整的,全部用過內存都放到一邊,空閒的內存放在另外一邊,中間放着一個指針做爲分界點的指示器,那分配內存就僅僅是把指針向空閒空間那邊挪動一段與對象大小相等的距離;
  • 空閒列表法:若堆內存不規整,就沒法經過簡單的移動指針分配內存。這種狀況下虛擬機會維護一個列表記錄哪些內存可用,分配時查找並更新列表;
        使用哪一種方式取決於內存是否規整,而內存是否規整又取決於垃圾收集器的GC算法。典型如serial、parnew這兩種垃圾收集器,它們在GC時帶有壓縮整理的功能,所以系統會採用「指針碰撞」的方式分配內存;而CMS這種基於Mark-Sweep(標記-清除)算法的垃圾收集器,則會採用「空閒列表法」分配內存。
(2)內存分配的安全
        須要注意的是,若多個線程同時申請分配內存,若是不加以同步控制,則會致使內存分配不對。不一樣的虛擬機會採用不一樣的機制避免線程安全問題:
  • 同步鎖定:經過CAS配上失敗重試的方式保證更新操做的原子性。注:CAS,即CPU硬件同步原語,全稱爲compare and swap(比較並交換),若比較不對則失敗;
  • TLAB:即線程分配緩衝區。在堆中預先爲每一個線程分配一小塊內存,線程在各自分配的內存上進行內存分配來保證安全。只有當TLAB用盡並申請新的TLAB時,才進行同步鎖定。

3.3.內存初始化

        內存初始化指的是將對象分配到的內存全部位重置爲0(不含對象頭)。若對象經過TLAB分配的,該過程會提早至「內存分配」執行.

3.4.對象頭初始化

        設置對象的對象頭信息。

3.5.對象實例數據初始化

        設置對象的實例數據信息,即成員變量值。只有這步完成了,一個真正的對象才產生並能提供給咱們使用。
相關文章
相關標籤/搜索