HotSpot的Java對象模型之Oop-Klass模型詳解
Java對象模型
Java中Object類是所有對象的父類,這是語言層面的定義。
在JVM層面,不僅Java類是對象,Java 方法也是對象, 字節(jié)碼常量池也是對象,一切皆是對象。
JVM使用不同的oop-klass模型來表示各種不同的對象。
而在技術落地時,這些不同的模型就使用不同的 oop 類(instanceoop methodoop constmethodoop等等)和 klass 類來表示 。
由于JVM使用C/C++編寫,因此這些 oop 和 klass 類便是各種不同的C++類。
對于Java類型與實例對象,只叫使用 instanceOop 和 instanceKlass 這 2 個 C++類來表示。
refrence、oop、klass的關系如下所示:

Oop繼承體系
oop(ordinary object pointer,普通對象指針)。是HotSpot用來表示Java對象的實例信息的一個體系
既在JVM層面,oop用于表示對象(oop本質上是一個指向內存中對象的起始存儲位置的指針)。
在hotspot/share/oops/oopsHierarchy.hpp 文件中,對oop的定義如下:
typedef class oopDesc* oop; typedef class instanceOopDesc* instanceOop; typedef class arrayOopDesc* arrayOop; typedef class objArrayOopDesc* objArrayOop; typedef class typeArrayOopDesc* typeArrayOop;
其中oop是Oop體系中的最高父類,整個繼承體系如下所示,不同的oop用于表示不同的類
例如instanceOop表示Java中普通的對象,arrayOop則表示數(shù)組對象。

oop(對象)由對象頭,對象體(實例數(shù)據(jù)),對齊填充三部分組成。

對象頭
對象頭中存儲了對象很多java內部的信息,如hash碼,對象所屬的年代,對象鎖,鎖狀態(tài)標志,偏向鎖(線程)ID,偏向時間等
Java對象頭一般占有2個機器碼(機器一次處理的數(shù)據(jù)位數(shù))。
如果對象是數(shù)組類型,則需要3個機器碼,因為JVM虛擬機可以通過Java對象的元數(shù)據(jù)信息確定Java對象的大小,但是無法從數(shù)組的元數(shù)據(jù)來確認數(shù)組的大小,所以用一塊區(qū)域用來記錄數(shù)組長度。
HotSpot虛擬機的對象頭包括兩部分信息
第一部分為 Mark Word
第二部分為 class pointer
如果是數(shù)組對象,那么還有數(shù)組長度。
普通對象和數(shù)組對象的對象頭結構如下(32位為例):


Mark Word
這部分主要用來存儲對象自身的運行時數(shù)據(jù),如hashcode、gc分代年齡等。
mark word 的位長度為JVM的一個機器碼的大小,為了存儲更多的信息,JVM將機器碼的最低兩個位設置為標記位,不同標記位下的Mark Word示意如下:


其中各部分的含義如下:
- lock: 2位的鎖狀態(tài)標記位,由于希望用盡可能少的二進制位表示盡可能多的信息,所以設置了lock標記。該標記的值不同,整個mark word表示的含義不同。
- biased_lock : lock 狀態(tài)(2位。和是否偏向鎖共同作用,示意如下)
- 0 01 無鎖
- 1 01 偏向鎖
- 0 00 輕量級鎖
- 0 10 重量級鎖
- 0 11 GC標記
- biased_lock:對象是否啟用偏向鎖標記,只占1個二進制位。為1時表示對象啟用偏向鎖,為0時表示對象沒有偏向鎖。
- age:4位的Java對象年齡。在GC中,如果對象在Survivor區(qū)復制一次,年齡增加1。當對象達到設定的閾值時,將會晉升到老年代。默認情況下,并行GC的年齡閾值為15,并發(fā)GC的年齡閾值為6。由于age只有4位,所以最大值為15,這就是-XX:MaxTenuringThreshold選項最大值為15的原因。
- identity_hashcode:25位的對象標識Hash碼,采用延遲加載技術。調用方法System.identityHashCode()計算,并會將結果寫到該對象頭中。當對象被鎖定時,該值會移動到管程Monitor中。
- thread:持有偏向鎖的線程ID。
- epoch:偏向時間戳。
- ptr_to_lock_record:指向棧中鎖記錄的指針。
- ptr_to_heavyweight_monitor:指向管程Monitor的指針。
64位與32位組成相同,主要是存儲位數(shù)長度不同:

Klass Pointer
這一部分用于存儲對象的類型指針,該指針指向它的類元數(shù)據(jù),JVM通過這個指針確定對象是哪個類的實例。
如果應用的對象過多,使用64位的指針將浪費大量內存,統(tǒng)計而言,64位的JVM將會比32位的JVM多耗費50%的內存。為了節(jié)約內存可以使用選項+UseCompressedOops開啟指針壓縮。
開啟該選項后,下列指針將壓縮至32位:每個Class的屬性指針(即靜態(tài)變量) 每個對象的屬性指針(即對象變量) 普通對象數(shù)組的每個元素指針
當然,也不是所有的指針都會壓縮,一些特殊類型的指針JVM不會優(yōu)化,比如指向PermGen的Class對象指針(JDK8中指向元空間的Class對象指針)、本地變量、堆棧元素、入參、返回值和NULL指針等。
array length
如果對象是一個數(shù)組,那么對象頭還需要有額外的空間用于存儲數(shù)組的長度,這部分數(shù)據(jù)的長度也是一個機器碼。64位JVM如果開啟+UseCompressedOops選項,該區(qū)域長度也將由64位壓縮至32位。
對象體(實例數(shù)據(jù))
對象體存儲的是具體的成員屬性。
值得注意的是,如果成員屬性屬于普通對象類型,則 oop 只存儲它的地址。
每個field在 oop 中都有一個對應的偏移量(offset), oop 通過該偏移量得到該field的地址,再根據(jù)地址得到具體數(shù)據(jù)。
因此,Java對象中的field存儲的并不是對象本身,而是對象的地址。
JVM將Java對象的field存儲在 oop 的對象體中, oop 提供了一系列的方法來獲取和設置field,并且針對每種基礎類型都提供了特有的實現(xiàn)。
對齊填充
由于虛擬機要求 對象起始地址必須是8字節(jié)的整數(shù)倍。
填充數(shù)據(jù)不是必須存在的,僅僅是為了字節(jié)對齊。
Klass繼承體系
與oop類似,klass的繼承體系如下:

// hotspot/src/share/vm/oops/oopsHierarchy.hpp ... class Klass; // Klass繼承體系的最高父類 class InstanceKlass; // 表示一個Java普通類,包含了一個類運行時的所有信息 class InstanceMirrorKlass; // 表示java.lang.Class class InstanceClassLoaderKlass; // 主要用于遍歷ClassLoader繼承體系 class InstanceRefKlass; // 表示java.lang.ref.Reference及其子類 class ArrayKlass; // 表示一個Java數(shù)組類 class ObjArrayKlass; // 普通對象的數(shù)組類 class TypeArrayKlass; // 基礎類型的數(shù)組類 ...
當然,JVM本身所定義的用于描述Java類的C++類也使用klass去描述,這相當于使用另一種面向對象的機制去描述C++類這種本身便是面向對象的數(shù)據(jù)。
(1)用于表示Java類。klass包含元數(shù)據(jù)和方法信息,用來描述 Java 類或者JVM內部自帶的C++類型信息。Java 類的繼承信息、成員變量 、靜態(tài)變量 、成員方法 、構造函數(shù)等信息都在 klass 中保存 ,一個class文件被JVM加載之后,就會被解析成一個klass對象存儲在內存中。JVM據(jù)此在運行期可以反射出Java類的全部結構信息。
(2)實現(xiàn)對象的虛分派(virtual dispatch)。所謂的虛分派,是JVM用來實現(xiàn)多態(tài)的一種機制。
體系總覽(待補充)
在JVM內部定義了3種結構去描述一種類型 :oop 、klass 和 handle 類。
注意,這 3 種數(shù)據(jù)結構不僅能夠描述外在的 Java 類 ,也能夠描述 JVM內在的C++類型對象。
Handle是對 oop 的行為的封裝(Handle類內部只有一個成員變量一handle,該變量類型是oop*,因此該變量最終指向的就是一個oop的首地址),在訪問 Java 類時一定是通過 handle 內部指針得到 oop 實例的,
再通過 oop 就能拿到 klass ,如此 handle 最終便能操縱 oop 的行為了(注意,如果是調用JVM內部C++類型所對應的oop的函數(shù) ,則不需要通過 handle 來中轉,直接通過 oop 拿到指定的 klass便能實現(xiàn))。
klass 不僅包含自己所固有的行為接口,而且也能夠操作 Java 類的函數(shù)。由于Java 函數(shù)在JVM內部都被表示成虛函數(shù),因此handle模型其實就是 Java 類行為的表達。
三者的關系如下:

Handle體系
handle封裝了oop,由于通過oop可以拿到 klass ,而 klass 是對 Java 類數(shù)據(jù)結構和方法的描述 ,因此 handle 間接封裝了 klass。
JVM內部使用一個 table 來存儲 oop 指針。但是JVM內部采用這種結構對klass進行間接引用是為GC考慮。具體表現(xiàn)在2個地方 :
通過handle,能夠讓 GC 知道其內部代碼都有哪些地方持有 GC 所管理的對象的引用,這只需要掃描 handle 所對應的 table ,這樣 JVM 便無須關注其內部到底哪些地方持有對普通對象的引用。
在GC過程中如果發(fā)生了對象移動(例如從新生代移到了老年代),那么JVM的內部引用無須跟著更改為被移動對象的新地址,JVM 只需要更改 handle table 里對應的指針即可 。

之所以分別給 oop 和 klass 定義了 2 套不同的 handle 體系,是為了方便垃圾回收。
本質上,每一個oop,其實都是一個 C++類型,也即 klass;而對于每一個 klass 所對應的 class ,在JVM內部又都會被封裝成 oop。
在具體描述一個類型時,會使用 oop 去存儲這個類型的實例數(shù)據(jù),并使用 klass 去存儲這個類型的元數(shù)據(jù)和虛方法表。而當一個類型完成其生命周期后,JVM會觸發(fā) GC 去回收,在回收時,既要回收一個類實例所對應的實例數(shù)據(jù) oop , 也要回收其所對應的元數(shù)據(jù)和虛方法表(當然,兩者并不是同時回收,一個是堆區(qū)的垃圾回收, 一個是永久區(qū)的垃圾回收)。
為了讓 GC 既能回收 oop 也能回收 klass,因此 oop 本身被封裝成了 oop ,而 klass 也被封裝成 oop。
到此這篇關于HotSpot的Java對象模型之Oop-Klass模型詳解的文章就介紹到這了,更多相關HotSpot的Oop-Klass模型內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
通過Spring Security魔幻山谷講解獲取認證機制核心原理
這篇文章主要介紹了通過Spring Security魔幻山谷講解獲取認證機制核心原理,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04
什么是 Java 的 CyclicBarrier(代碼示例)
CyclicBarrier 是多線程協(xié)同的利器,適合需要多次同步的場景,本文通過代碼示例講解什么是 Java 的 CyclicBarrier,感興趣的朋友一起看看吧2025-03-03
詳解Java中l(wèi)og4j.properties配置與加載應用
這篇文章主要介紹了 log4j.properties配置與加載應用的相關資料,需要的朋友可以參考下2018-02-02
WebSocket+Vue+SpringBoot實現(xiàn)語音通話的使用示例
本文主要介紹了WebSocket+Vue+SpringBoot實現(xiàn)語音通話的使用示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-11-11

