Java對象經過Oop來表示。Oop指的是 Ordinary Object Pointer(普通對象指針)。在 Java 建立對象實例的時候建立,用於表示對象的實例信息。也就是說,在 Java 應用程序運行中每建立一個 Java 對象,在 JVM 內部都會建立一個 Oop 對象來表示 Java 對象。
Oop涉及到的相關類的繼承關係以下圖所示。html
oopDesc的一個別名爲oop,因此HotSpot中通常會使用oop來表示oopDesc類型。java
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字節。jvm
啓用-XX:+UseCompressedOops命令後,主要會壓縮以下的一些對象: ide
固然,壓縮也不是全部的指針都會壓縮,對一些特殊類型的指針,HotSpot是不會優化的,例如指向Metaspace的Class對象指針、本地變量、堆棧元素、入參、返回值和NULL指針不會被壓縮。 函數
64位地址分爲堆的基地址+偏移量,當堆內存小於32GB時候,在壓縮過程當中,把偏移量除以8後的結果保存到32位地址。當解壓時再把32位地址放大8倍,因此啓用-XX:+UseCompressedOops命令的條件是堆內存要在4GB*8=32GB之內。具體實現方式是在機器碼中植入壓縮與解壓指令,可能會給JVM增長額外的開銷。oop
總結一下:佈局
聯合體中定義的_klass或_compressed_klass指針指向的是Klass實例,這個Klass實例保存了Java對象的實際類型,也就是Java對象所對應的Java類。 post
調用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的內存首地址的偏移量。
上面介紹oopDesc類時,能夠看到定義了一個屬性_mark,而類型爲markOop,其實這是markOopDesc的別名。markOopDesc類的實例能夠表示Java對象頭信息的「Mark Word",包含的信息有哈希碼、GC分代年齡、偏向鎖標記、線程持有的鎖、偏向線程ID、偏向時間戳等。
markOopDesc類的實例並不能表示一個具體的Java對象,而是經過一個字的各個位來表示Java對象的頭信息。對於32位系統來講,一個字爲32位(4字節),而對於64位系統來講,一個字有64位(8字節)。因爲目前64位是主流,因此筆者不在對32位的結構進行說明。
下圖表示了在Java對象不一樣狀態下的Mark Word各個位區間的含義。
上面每一行表明對象處於某種狀態時的樣子。其中各部分的含義以下:
關於鎖與鎖升級相關的內容,後續文章會詳細介紹,這裏只須要大概認識一下相關的字段便可。
相關文章的連接以下:
一、在Ubuntu 16.04上編譯OpenJDK8的源代碼
關注公衆號,有HotSpot源碼剖析系列文章!
參考文章: