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)標志,偏向鎖(線程)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ù)組對象的對象頭結(jié)構(gòu)如下(32位為例):
Mark Word
這部分主要用來存儲對象自身的運行時數(shù)據(jù),如hashcode、gc分代年齡等。
mark word 的位長度為JVM的一個機器碼的大小,為了存儲更多的信息,JVM將機器碼的最低兩個位設(shè)置為標記位,不同標記位下的Mark Word示意如下:
其中各部分的含義如下:
- lock: 2位的鎖狀態(tài)標記位,由于希望用盡可能少的二進制位表示盡可能多的信息,所以設(shè)置了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ū)復(fù)制一次,年齡增加1。當(dāng)對象達到設(shè)定的閾值時,將會晉升到老年代。默認情況下,并行GC的年齡閾值為15,并發(fā)GC的年齡閾值為6。由于age只有4位,所以最大值為15,這就是-XX:MaxTenuringThreshold選項最大值為15的原因。
- identity_hashcode:25位的對象標識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ù)組的長度,這部分數(shù)據(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)。
對齊填充
由于虛擬機要求 對象起始地址必須是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制去描述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)的一種機制。
體系總覽(待補充)
在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 類行為的表達。
三者的關(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進行間接引用是為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)文章
Springboot中使用攔截器、過濾器、監(jiān)聽器的流程分析
Javaweb三大組件:servlet、Filter(過濾器)、?Listener(監(jiān)聽器),這篇文章主要介紹了Springboot中使用攔截器、過濾器、監(jiān)聽器的流程分析,感興趣的朋友跟隨小編一起看看吧2023-12-12win10 eclipse配置環(huán)境變量的教程圖解
本文通過圖文并茂的形式給大家介紹了win10 eclipse配置環(huán)境變量的方法,非常不錯,具有一定的參考借鑒價值,需要的朋友參考下吧2018-07-07springboot druid數(shù)據(jù)庫配置密碼加密的實現(xiàn)
Druid是阿里開發(fā)的數(shù)據(jù)庫連接池,本文主要介紹了springboot druid數(shù)據(jù)庫配置密碼加密的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2024-06-06Windows同時配置兩個jdk環(huán)境變量的操作步驟
Java Development Kit (JDK) 是開發(fā)Java應(yīng)用程序的基礎(chǔ),包含了編譯器、調(diào)試器以及其他必要的工具,本指南將一步步指導(dǎo)您完成在Windows操作系統(tǒng)上同時配置兩個jdk環(huán)境變量的操作步驟,需要的朋友可以參考下2024-09-09解決Feign切換client到okhttp無法生效的坑(出現(xiàn)原因說明)
這篇文章主要介紹了解決Feign切換client到okhttp無法生效的坑(出現(xiàn)原因說明),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02