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

Oop繼承體系
oop(ordinary object pointer,普通對象指針)。是HotSpot用來表示Java對象的實例信息的一個體系
既在JVM層面,oop用于表示對象(oop本質(zhì)上是一個指向內(nèi)存中對象的起始存儲位置的指針)。
在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內(nèi)部的信息,如hash碼,對象所屬的年代,對象鎖,鎖狀態(tài)標(biāo)志,偏向鎖(線程)ID,偏向時間等
Java對象頭一般占有2個機(jī)器碼(機(jī)器一次處理的數(shù)據(jù)位數(shù))。
如果對象是數(shù)組類型,則需要3個機(jī)器碼,因為JVM虛擬機(jī)可以通過Java對象的元數(shù)據(jù)信息確定Java對象的大小,但是無法從數(shù)組的元數(shù)據(jù)來確認(rèn)數(shù)組的大小,所以用一塊區(qū)域用來記錄數(shù)組長度。
HotSpot虛擬機(jī)的對象頭包括兩部分信息
第一部分為 Mark Word
第二部分為 class pointer
如果是數(shù)組對象,那么還有數(shù)組長度。
普通對象和數(shù)組對象的對象頭結(jié)構(gòu)如下(32位為例):


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


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

Klass Pointer
這一部分用于存儲對象的類型指針,該指針指向它的類元數(shù)據(jù),JVM通過這個指針確定對象是哪個類的實例。
如果應(yīng)用的對象過多,使用64位的指針將浪費大量內(nèi)存,統(tǒng)計而言,64位的JVM將會比32位的JVM多耗費50%的內(nèi)存。為了節(jié)約內(nèi)存可以使用選項+UseCompressedOops開啟指針壓縮。
開啟該選項后,下列指針將壓縮至32位:每個Class的屬性指針(即靜態(tài)變量) 每個對象的屬性指針(即對象變量) 普通對象數(shù)組的每個元素指針
當(dāng)然,也不是所有的指針都會壓縮,一些特殊類型的指針JVM不會優(yōu)化,比如指向PermGen的Class對象指針(JDK8中指向元空間的Class對象指針)、本地變量、堆棧元素、入?yún)?、返回值和NULL指針等。
array length
如果對象是一個數(shù)組,那么對象頭還需要有額外的空間用于存儲數(shù)組的長度,這部分?jǐn)?shù)據(jù)的長度也是一個機(jī)器碼。64位JVM如果開啟+UseCompressedOops選項,該區(qū)域長度也將由64位壓縮至32位。
對象體(實例數(shù)據(jù))
對象體存儲的是具體的成員屬性。
值得注意的是,如果成員屬性屬于普通對象類型,則 oop 只存儲它的地址。
每個field在 oop 中都有一個對應(yīng)的偏移量(offset), oop 通過該偏移量得到該field的地址,再根據(jù)地址得到具體數(shù)據(jù)。
因此,Java對象中的field存儲的并不是對象本身,而是對象的地址。
JVM將Java對象的field存儲在 oop 的對象體中, oop 提供了一系列的方法來獲取和設(shè)置field,并且針對每種基礎(chǔ)類型都提供了特有的實現(xiàn)。
對齊填充
由于虛擬機(jī)要求 對象起始地址必須是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; // 基礎(chǔ)類型的數(shù)組類 ...
當(dāng)然,JVM本身所定義的用于描述Java類的C++類也使用klass去描述,這相當(dāng)于使用另一種面向?qū)ο蟮臋C(jī)制去描述C++類這種本身便是面向?qū)ο蟮臄?shù)據(jù)。
(1)用于表示Java類。klass包含元數(shù)據(jù)和方法信息,用來描述 Java 類或者JVM內(nèi)部自帶的C++類型信息。Java 類的繼承信息、成員變量 、靜態(tài)變量 、成員方法 、構(gòu)造函數(shù)等信息都在 klass 中保存 ,一個class文件被JVM加載之后,就會被解析成一個klass對象存儲在內(nèi)存中。JVM據(jù)此在運行期可以反射出Java類的全部結(jié)構(gòu)信息。
(2)實現(xiàn)對象的虛分派(virtual dispatch)。所謂的虛分派,是JVM用來實現(xiàn)多態(tài)的一種機(jī)制。
體系總覽(待補(bǔ)充)
在JVM內(nèi)部定義了3種結(jié)構(gòu)去描述一種類型 :oop 、klass 和 handle 類。
注意,這 3 種數(shù)據(jù)結(jié)構(gòu)不僅能夠描述外在的 Java 類 ,也能夠描述 JVM內(nèi)在的C++類型對象。
Handle是對 oop 的行為的封裝(Handle類內(nèi)部只有一個成員變量一handle,該變量類型是oop*,因此該變量最終指向的就是一個oop的首地址),在訪問 Java 類時一定是通過 handle 內(nèi)部指針得到 oop 實例的,
再通過 oop 就能拿到 klass ,如此 handle 最終便能操縱 oop 的行為了(注意,如果是調(diào)用JVM內(nèi)部C++類型所對應(yīng)的oop的函數(shù) ,則不需要通過 handle 來中轉(zhuǎn),直接通過 oop 拿到指定的 klass便能實現(xiàn))。
klass 不僅包含自己所固有的行為接口,而且也能夠操作 Java 類的函數(shù)。由于Java 函數(shù)在JVM內(nèi)部都被表示成虛函數(shù),因此handle模型其實就是 Java 類行為的表達(dá)。
三者的關(guān)系如下:

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

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

