Java 中的對(duì)象在 JVM 中是怎么映射
寫在前面
Java 中的對(duì)象在 JVM 中是怎么映射的?這個(gè)話題一直想寫。但是一直沒有動(dòng)筆。后來發(fā)現(xiàn) Java 中的鎖很多問題都與這個(gè)在 JVM 中映射的對(duì)象存在著關(guān)系。還是需要搞定它。
我們平時(shí)在寫 Java 代碼的時(shí)候,最常見的就是創(chuàng)建一個(gè)對(duì)象了。這些代碼最終都是會(huì)在虛擬機(jī)上運(yùn)行的。而一個(gè)對(duì)象最終在 JVM 中呈現(xiàn)的樣子到底是什么呢?還是非常值得我們探究一番。畢竟虛擬機(jī) HotSpot 是 C++ 實(shí)現(xiàn)的。
探尋過程
HotSpot 設(shè)計(jì)的一套 OOP-Klass 模型用來在 JVM 內(nèi)部進(jìn)行表示一個(gè)對(duì)象。這里我們需要提到一個(gè)詞語 OOP-Klass 二分模型。先從這個(gè)單詞的含義來理解它。OOP 是 oridinary object pointer,即普通對(duì)象指針,它是用來描述對(duì)象的實(shí)例信息。而 Klass 是代表 Java 類的 C++ 對(duì)等體,用來描述 Java 類。
Oops模塊可以分為兩個(gè)獨(dú)立的部分:OOP 框架與 Klass 框架。
1.探尋 OOP 框架
在 Java 應(yīng)用程序過程中,每次創(chuàng)建一個(gè) Java 對(duì)象的時(shí)候,在 JVM 內(nèi)部也會(huì)相應(yīng)地創(chuàng)建一個(gè) OOP 對(duì)象來表示一個(gè) Java 對(duì)象。OOPS 類的共同基類為 oopDesc。
class?oopDesc?{
??friend?class?VMStructs;
?private:
??volatile?markOop??_mark;
??union?_metadata?{
????wideKlassOop????_klass;
????narrowOop???????_compressed_klass;
??}?_metadata;
在 HotSpot 中,根據(jù) JVM 內(nèi)部使用的對(duì)象業(yè)務(wù)類型,分成了多種 oopDesc 子類。每種類型的 OOP 都代表了一個(gè)在 JVM 內(nèi)部使用的特定對(duì)象模型。有點(diǎn)接口和具體實(shí)現(xiàn)類的感覺了。
| 類 | 作用 |
|---|---|
| oopDesc | OOPS 抽象基類 |
| instanceOopDesc | 描述 Java 類的實(shí)例 |
| methodOopDesc | 描述 Java 方法 |
| constMethodOopDesc | 描述 Java 方法的只讀信息 |
| methodDataOopDesc | 描述 Java 方法的信息 |
| arrayOopDesc | 描述數(shù)組的抽象基類 |
| objArrayOopDesc | 描述容納對(duì)象( OOPS ) 元素的數(shù)組 |
| typeArrayOopDesc | 描述容納基本類型(非 OOPS )的數(shù)組 |
| constantPoolOopDesc | 描述容納類文件中常量池項(xiàng)的數(shù)組 |
| constantPoolCacheOopDesc | 描述常量池高速緩存 |
| klassOopDesc | 描述一個(gè) Java 類 |
| markOopDesc | 描述對(duì)象頭 |
當(dāng)我們?cè)?Java 中使用 new 創(chuàng)建一個(gè) Java 實(shí)例對(duì)象的時(shí)候,JVM 會(huì)相應(yīng)的創(chuàng)建一個(gè)instanceOopDesc 對(duì)象來表示這個(gè) Java 對(duì)象。當(dāng)我們使用 new 創(chuàng)建一個(gè) Java 數(shù)組實(shí)例的時(shí)候,JVM 就會(huì)創(chuàng)建一個(gè) arrayOopDesc 對(duì)象來代表這個(gè)數(shù)組對(duì)象。
一個(gè)對(duì)象在堆內(nèi)存的存儲(chǔ)布局可以分為三個(gè)部分,對(duì)象頭( Header )、實(shí)例數(shù)據(jù)(Instance Data)、和對(duì)齊填充( Padding )。而 instanceOopDesc 或者 arrayOopDesc 就是我們提到的對(duì)象頭。
對(duì)象頭里面包含了哪些信息呢,它包含了兩部分的信息:
一部分是存儲(chǔ)對(duì)象運(yùn)行時(shí)記錄信息,如哈希碼( HashCode )、GC 年代分布、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程 ID、偏向時(shí)間戳等。很多地方也稱這部分為 MarkWord。
另一部分是元數(shù)據(jù)指針,指向描述類型的 Klass 對(duì)象的指針。Klass 對(duì)象包含了實(shí)例對(duì)象所屬類型的元數(shù)據(jù)(meta data)。Java 虛擬機(jī)通過這個(gè)指針來確定該對(duì)象是哪個(gè)類的實(shí)例。在運(yùn)行的時(shí)候會(huì)使用這個(gè)指針定位到位于方法區(qū)的類型信息。
如果對(duì)象是一個(gè) Java 數(shù)組的話,那在對(duì)象頭中還有一塊用于記錄數(shù)組長(zhǎng)度的數(shù)據(jù)。

對(duì)象的對(duì)齊填充,并不是一定需要存在的。它也沒有啥特別大的作用。僅僅是起著占位符的作用。因?yàn)?HotSpot 虛擬機(jī)要求對(duì)象的起始地址必須是 8 字節(jié)的整數(shù)倍。也即任何對(duì)象的大小必須是 8 字節(jié)的整數(shù)倍。如果不夠的時(shí)候就需要這個(gè)進(jìn)行補(bǔ)齊。
當(dāng)我們?cè)?Java 程序里面,使用 new 關(guān)鍵字創(chuàng)建對(duì)象的時(shí)候,對(duì)象的引用保存在棧上,在堆中分配對(duì)象實(shí)例。這個(gè)除了實(shí)例數(shù)據(jù)外,JVM 還會(huì)在實(shí)例數(shù)據(jù)的前面自動(dòng)加上一個(gè)對(duì)象頭 instanceOopDesc 。對(duì)象的元數(shù)據(jù)( instanceKlass )保存在方法區(qū)中。
順便回顧一下方法區(qū)是保存什么數(shù)據(jù)的,方法區(qū)是用來存儲(chǔ)被虛擬機(jī)加載的類型信息、常量、靜態(tài)常量、即使編譯器編譯后的代碼緩存等數(shù)據(jù)。
通過棧上引用可以訪問到 JVM 內(nèi)部表示的該對(duì)象實(shí)例(instanceOop)。當(dāng)需要調(diào)用該類的方法或者訪問該類的類變量的時(shí)候。就是通過 instanceOop 持有的類元數(shù)據(jù)指針定位到方法區(qū)中的 instanceKlass 對(duì)象來完成。

2.探尋 Klass 框架
Klass 數(shù)據(jù)結(jié)構(gòu):描述類型自身的布局,以及刻畫出于其他類間的關(guān)系(父類、子類、兄弟類)。Klass 是一個(gè)頂層的基類。
這里主要說一下 instanceKlass, JVM 在運(yùn)行的時(shí)候,為每一個(gè)已經(jīng)加載的 Java 類創(chuàng)建一個(gè)instanceKlass 對(duì)象。用在 JVM 層表示 Java 類。
instanceKlass 內(nèi)存布局如下:

??//類擁有的方法列表
??objArrayOop?????_methods;
??//描述方法順序
??typeArrayOop????_method_ordering;
??//實(shí)現(xiàn)的接口
??objArrayOop?????_local_interfaces;
??//繼承的接口
??objArrayOop?????_transitive_interfaces;
??//域
??typeArrayOop????_fields;
??//常量
??constantPoolOop?_constants;
??//類加載器
??oop?????????????_class_loader;
??//protected域
??oop?????????????_protection_domain;
??klassOop????????_host_klass;
??objArrayOop?????_signers;
??typeArrayOop????_inner_classes;
??klassOop????????_implementors?0;
??klassOop????????_implementors?1;
??typeArrayOop????_class_annotations;
??objArrayOop?????_fields_annotations;
??objArrayOop?????_methods_annotations;
??objArrayOop?????_methods_parameter_annotations;?
??objArrayOop?????_methods_default_annotations;
以上是 OOP 塊的內(nèi)容,在 JVM 中,對(duì)象在內(nèi)存中的基本存在形式就是 OOP。
寫在后面
通過以上的探索,基本上有以下的共識(shí)。很重要的兩個(gè)詞匯instanceOopDesc、instanceKlass需要在腦海中留下一些印象。
對(duì)象頭 instanceOopDesc 包含了 MarkWord 與 元數(shù)據(jù)指針。而 instanceKlass 是用來在JVM 層面表示一個(gè) Java 類的(保存在方法區(qū))。其中元數(shù)據(jù)指針就是指向 JVM 層表示一個(gè) Java 對(duì)象的 instanceKlass 。Klass 對(duì)象包含了實(shí)例對(duì)象所屬類型的元數(shù)據(jù)(meta data)。自然指向它的就被稱為了元數(shù)據(jù)指針了。
MarkWord 里面就包含了與 Java 中鎖相關(guān)的信息了。這個(gè)后面在寫一篇專門論述。
