HotSpot的對象模型(5)


Java對象通過Oop來表示。Oop指的是 Ordinary Object Pointer(普通對象指針)。在 Java 創建對象實例的時候創建,用於表示對象的實例信息。也就是說,在 Java 應用程序運行中每創建一個 Java 對象,在 JVM 內部都會創建一個 Oop 對象來表示 Java 對象。
Oop涉及到的相關類的繼承關系如下圖所示。

 

1、oopDesc類

oopDesc的一個別名為oop,所以HotSpot中一般會使用oop來表示oopDesc類型。

oopDesc 是 所 有 的 類 名 為 xxxOopDesc 格 式 的 類 的 基 類 , 這 些 類 的 實 例 表 示 Java 對 象,所以xxxOopDesc 格式的類中會聲明一些保存 Java 對象的字段,並且也可以直接被 C++獲取。類及重要屬性的定義如下:

位置:/openjdk/hotspot/src/share/vm/oops/oop.hpp
class oopDesc {
...
private:
 volatile markOop _mark; 
 union _metadata {
    Klass*   _klass;
    narrowKlass _compressed_klass;
 } _metadata;
...
}

Java對象內存布局主要分為header(頭部)和fields(實例字段)。header由_mark和_metadata組成。_mark字段保存了Java對象的一些信息,如GC年齡,鎖狀態等;_metadata使用聯合體(union)來聲明 ,這樣是為了在 64 位機器上能對指針進行壓縮。因為從32位平台到64位時,主要就是指針由4字節變為了8字節,所以通常64位HotSpot消耗的內存會比32位的大,造成堆內存損失,不過從JDK 1.6 update14開始,64位的JVM正式支持了-XX:+UseCompressedOops(默認開啟)。這個可以壓縮指針,起到節約內存占用的作用。

在64位系統下,存放_metadata的空間大小是8字節,_mark是8字節,對象頭為16字節。64位開啟指針壓縮的情況下,存放_metadata的空間大小是4字節,_mark是8字節,對象頭為12字節。

啟用-XX:+UseCompressedOops命令后,主要會壓縮如下的一些對象: 

  • 每個Class的屬性指針(靜態成員變量);
  • 每個對象的屬性指針;
  • 普通對象數組的每個元素指針。 

當然,壓縮也不是所有的指針都會壓縮,對一些特殊類型的指針,HotSpot是不會優化的,例如指向Metaspace的Class對象指針、本地變量、堆棧元素、入參、返回值和NULL指針不會被壓縮。 

64位地址分為堆的基地址+偏移量,當堆內存小於32GB時候,在壓縮過程中,把偏移量除以8后的結果保存到32位地址。當解壓時再把32位地址放大8倍,所以啟用-XX:+UseCompressedOops命令的條件是堆內存要在4GB*8=32GB以內。具體實現方式是在機器碼中植入壓縮與解壓指令,可能會給JVM增加額外的開銷。

總結一下:

  • 如果GC堆大小在4G以下,直接砍掉高32位,避免了編碼解碼過程;
  • 如果GC堆大小在4G以上32G以下,則啟用-XX:+UseCompressedOops命令;
  • 如果GC堆大小大於32G,壓指失效,使用原來的64位。
另外Java8使用Metaspace存儲元數據,在-XX:+UseCompressedOops命令之外,額外增加了一個新選項叫做-XX:+UseCompressedClassPointer。這個選項打開后,類元信息中的指針也用32bit的Compressed版本。而這些指針指向的空間被稱作“Compressed Class Space”。默認大小是1G,可以通過“CompressedClassSpaceSize”調整。 
由於UseCompressedClassPointers的開啟是依賴於UseCompressedOops的開啟,因此,要使UseCompressedClassPointers起作用,得先開啟UseCompressedOops,並且開啟UseCompressedOops 也默認強制開啟UseCompressedClassPointers,關閉UseCompressedOops 默認關閉UseCompressedClassPointers。

聯合體中定義的_klass或_compressed_klass指針指向的是Klass實例,這個Klass實例保存了Java對象的實際類型,也就是Java對象所對應的Java類。 

調用header_size()函數獲取header占用的內存空間的大小,具體實現如下: 

位置:/openjdk/hotspot/src/share/vm/oops/oop.inline.hpp
static int header_size() { 
   return sizeof(oopDesc)/HeapWordSize; 
}

計算占用的字的大小,對於64位機器來說,一個字的大小為8字節,所以HeapWordSize的值為8。

Java對象的header信息可以存儲到oopDesc類中定義的_mark和_metadata屬性上,而Java對象的fields沒有在oopDesc類中定義相應的屬性來存儲,所以只能申請一定大小的空間,然后按順序進行存儲。對象字段是存放在緊跟着oopDesc實例本身占用的內存空間之后的,在獲取時只能通過偏移來取值。

opDesc 類的field_base()函數可用於獲取字段的地址,實現如下:

位置:/openjdk/hotspot/src/share/vm/oops/oop.inline.hpp
inline void* field_base(int offset) const {
    return (void*)&(  (char*)this  )[offset];
}

offset是偏移量,計算相對於當前實例this的內存首地址的偏移量。關於在HotSpot中計算偏移量的方法在HotSpot源碼分析之C++對象的內存布局 一文中已經介紹過,這里不再介紹。 

2、markOopDesc類

上面介紹oopDesc類時,可以看到定義了一個屬性_mark,而類型為markOop,其實這是markOopDesc的別名。markOopDesc類的實例可以表示Java對象頭信息的“Mark Word",包含的信息有哈希碼、GC分代年齡、偏向鎖標記、線程持有的鎖、偏向線程ID、偏向時間戳等。

markOopDesc類的實例並不能表示一個具體的Java對象,而是通過一個字的各個位來表示Java對象的頭信息。對於32位系統來說,一個字為32位(4字節),而對於64位系統來說,一個字有64位(8字節)。由於目前64位是主流,所以筆者不在對32位的結構進行說明。

下圖表示了在Java對象不同狀態下的Mark Word各個位區間的含義。

 

上面每一行代表對象處於某種狀態時的樣子。其中各部分的含義如下:

  • lock:2位的鎖狀態標記位,由於希望用盡可能少的二進制位表示盡可能多的信息,所以設置了lock標記。該標記的值不同,整個Mark Word表示的含義不同。biased_lock和lock一起表示了鎖的狀態;
  • biased_lock:對象是否啟用偏向鎖標記,只占1個二進制位。為1時表示對象啟用偏向鎖,為0時表示對象沒有偏向鎖。lock和biased_lock共同表示對象的鎖狀態;
  • age:占用4個二進制位,存儲的是Java對象的年齡。在GC中,如果對象在Survivor區復制一次,年齡增加1。當對象達到設定的閾值時,將會晉升到老年代。默認情況下,並行GC的年齡閾值為15,並發GC的年齡閾值為6。由於age只有4位,所以最大值為15,這就是-XX:MaxTenuringThreshold選項最大值為15的原因;
  • identity_hashcode:占用31個二進制位,用來存儲對象的HashCode,采用延遲加載技術。調用方法System.identityHashCode()計算,並會將結果寫到該對象頭中。如果當前對象的鎖狀態為偏向鎖,由於偏向鎖沒有存儲HashCode的地方,所以調用identityHashCode()方法會造成鎖升級,而輕量級鎖和重量級鎖所指向的lock record或monitor都有存儲HashCode的空間。hashCode 只針對 identity hash code。用戶自定義的 hashCode() 方法所返回的值不存在 Mark Word 中。Identity hash code 是未被覆寫的 java.lang.Object.hashCode() 或者 java.lang.System.identityHashCode(Object) 所返回的值;
  • thread:持有偏向鎖的線程ID;
  • epoch:偏向鎖的時間戳;
  • ptr_to_lock_record:輕量級鎖狀態下,指向棧中鎖記錄的指針;
  • ptr_to_heavyweight_monitor:重量級鎖狀態下,指向對象監視器Monitor的指針。

關於鎖與鎖升級相關的內容,后續文章會詳細介紹,這里只需要大概認識一下相關的字段即可。 

typedef class   markOopDesc*    markOop;

源代碼位置:runtime/basicLock.hpp

class BasicLock VALUE_OBJ_CLASS_SPEC {
 private:
  volatile markOop   _displaced_header;
  ...
} 

 

相關文章的鏈接如下:

1、在Ubuntu 16.04上編譯OpenJDK8的源代碼 

2、調試HotSpot源代碼

3、HotSpot項目結構 

4、HotSpot的啟動過程 

5、HotSpot二分模型(1)

6、HotSpot的類模型(2)  

7、HotSpot的類模型(3) 

8、HotSpot的類模型(4)

關注公眾號,有HotSpot源碼剖析系列文章!  

 

參考文章:

(1)JVM之壓縮指針(CompressedOops)

(2)JVM Anatomy Quark #23: Compressed References 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM