原文鏈接:深入理解多線(xiàn)程(二)—— Java的對(duì)象模型-HollisChuang's Blog
上一篇文章中簡(jiǎn)單介紹過(guò)synchronized關(guān)鍵字的方式恒序,其中瘦麸,同步代碼塊使用monitorenter和monitorexit兩個(gè)指令實(shí)現(xiàn),同步方法使用ACC_SYNCHRONIZED標(biāo)記符實(shí)現(xiàn)歧胁。后面幾篇文章會(huì)從JVM源碼的角度更加深入瞎暑,層層剝開(kāi)synchronized的面紗。
在進(jìn)入正題之前与帆,肯定有些基礎(chǔ)知識(shí)需要鋪墊了赌,那么先來(lái)看一下一個(gè)容易被忽略的但是又很重要的知識(shí)點(diǎn) —— Java對(duì)象模型 。
大家都知道的是玄糟,Java對(duì)象保存在堆內(nèi)存中勿她。在內(nèi)存中,一個(gè)Java對(duì)象包含三部分:對(duì)象頭阵翎、實(shí)例數(shù)據(jù)和對(duì)齊填充逢并。其中對(duì)象頭是一個(gè)很關(guān)鍵的部分之剧,因?yàn)?b>對(duì)象頭中包含鎖狀態(tài)標(biāo)志、線(xiàn)程持有的鎖等標(biāo)志砍聊。這篇文章就主要從Java對(duì)象模型入手背稼,找一找我們關(guān)系的對(duì)象頭以及對(duì)象頭中和鎖相關(guān)的運(yùn)行時(shí)數(shù)據(jù)在JVM中是如何表示的。
Java的對(duì)象模型
任何一個(gè)接觸過(guò)Java的人都知道玻蝌,Java是一種面向?qū)ο笳Z(yǔ)言蟹肘。在學(xué)習(xí)Java的過(guò)程中你一定對(duì)下面兩句話(huà)不陌生:
1、在面向?qū)ο蟮能浖懈┦鳎瑢?duì)象(Object)是某一個(gè)類(lèi)(Class)的實(shí)例帘腹。?維基百科
2、一切皆對(duì)象?Thinking In Java
我們還知道许饿,在JVM的內(nèi)存結(jié)構(gòu)中阳欲,對(duì)象保存在堆內(nèi)存中,而我們?cè)趯?duì)對(duì)象進(jìn)行操作時(shí)陋率,其實(shí)操作的是對(duì)象的引用球化。那么對(duì)象本身在JVM中的結(jié)構(gòu)是什么樣的呢?本文的所有分析均基于HotSpot虛擬機(jī)瓦糟。
oop-klass model
HotSpot是基于c++實(shí)現(xiàn)筒愚,而c++是一門(mén)面向?qū)ο蟮恼Z(yǔ)言,本身是具備面向?qū)ο蠡咎卣鞯睦暌常訨ava中的對(duì)象表示,最簡(jiǎn)單的做法是為每個(gè)Java類(lèi)生成一個(gè)c++類(lèi)與之對(duì)應(yīng)扯再。但HotSpot JVM并沒(méi)有這么做芍耘,而是設(shè)計(jì)了一個(gè)OOP-Klass Model。OOP(Ordinary Object Pointer)指的是普通對(duì)象指針熄阻,而Klass用來(lái)描述對(duì)象實(shí)例的具體類(lèi)型斋竞。
為什么HotSpot要設(shè)計(jì)一套o(hù)op-klass model呢?答案是:HotSopt JVM的設(shè)計(jì)者不想讓每個(gè)對(duì)象中都含有一個(gè)vtable(虛函數(shù)表)
這個(gè)解釋似乎可以說(shuō)得通秃殉。眾所周知坝初,C++和Java都是面向?qū)ο蟮恼Z(yǔ)言,面向?qū)ο笳Z(yǔ)言有一個(gè)很重要的特性就是多態(tài)钾军。關(guān)于多態(tài)的實(shí)現(xiàn)鳄袍,C++和Java有著本質(zhì)的區(qū)別蜀涨。
多態(tài)是面向?qū)ο蟮淖钪饕奶匦灾话段希且环N方法的動(dòng)態(tài)綁定,實(shí)現(xiàn)運(yùn)行時(shí)的類(lèi)型決定對(duì)象的行為努隙。多態(tài)的表現(xiàn)形式是父類(lèi)指針或引用指向子類(lèi)對(duì)象樱哼,在這個(gè)指針上調(diào)用的方法使用子類(lèi)的實(shí)現(xiàn)版本哀九。多態(tài)是IOC剿配、模板模式實(shí)現(xiàn)的關(guān)鍵。
在C++中通過(guò)虛函數(shù)表的方式實(shí)現(xiàn)多態(tài)阅束,每個(gè)包含虛函數(shù)的類(lèi)都具有一個(gè)虛函數(shù)表(virtual table)呼胚,在這個(gè)類(lèi)對(duì)象的地址空間的最靠前的位置存有指向虛函數(shù)表的指針。在虛函數(shù)表中息裸,按照聲明順序依次排列所有的虛函數(shù)蝇更。由于C++在運(yùn)行時(shí)并不維護(hù)類(lèi)型信息,所以在編譯時(shí)直接在子類(lèi)的虛函數(shù)表中將被子類(lèi)重寫(xiě)的方法替換掉界牡。
在Java中簿寂,在運(yùn)行時(shí)會(huì)維持類(lèi)型信息以及類(lèi)的繼承體系。每一個(gè)類(lèi)會(huì)在方法區(qū)中對(duì)應(yīng)一個(gè)數(shù)據(jù)結(jié)構(gòu)用于存放類(lèi)的信息宿亡,可以通過(guò)Class對(duì)象訪問(wèn)這個(gè)數(shù)據(jù)結(jié)構(gòu)常遂。其中,類(lèi)型信息具有superclass屬性指示了其超類(lèi)挽荠,以及這個(gè)類(lèi)對(duì)應(yīng)的方法表(其中只包含這個(gè)類(lèi)定義的方法克胳,不包括從超類(lèi)繼承來(lái)的)。而每一個(gè)在堆上創(chuàng)建的對(duì)象圈匆,都具有一個(gè)指向方法區(qū)類(lèi)型信息數(shù)據(jù)結(jié)構(gòu)的指針漠另,通過(guò)這個(gè)指針可以確定對(duì)象的類(lèi)型。
上面這段是我從網(wǎng)上摘取過(guò)來(lái)的跃赚,說(shuō)的有一定道理笆搓,但是也不全對(duì)。至于為啥纬傲,我會(huì)在后文介紹到Klass的時(shí)候細(xì)說(shuō)满败。
關(guān)于opp-klass模型的整體定義,在HotSpot的源碼中可以找到叹括。
oops模塊可以分成兩個(gè)相對(duì)獨(dú)立的部分:OOP框架和Klass框架算墨。
在oopsHierarchy.hpp里定義了oop和klass各自的體系。
oop-klass結(jié)構(gòu)
oop體系:
上面列出的是整個(gè)Oops模塊的組成結(jié)構(gòu)汁雷,其中包含多個(gè)子模塊净嘀。每一個(gè)子模塊對(duì)應(yīng)一個(gè)類(lèi)型,每一個(gè)類(lèi)型的OOP都代表一個(gè)在JVM內(nèi)部使用的特定對(duì)象的類(lèi)型侠讯。
從上面的代碼中可以看到挖藏,有一個(gè)變量opp的類(lèi)型是oppDesc?,OOPS類(lèi)的共同基類(lèi)型為oopDesc厢漩。
在Java程序運(yùn)行過(guò)程中熬苍,每創(chuàng)建一個(gè)新的對(duì)象,在JVM內(nèi)部就會(huì)相應(yīng)地創(chuàng)建一個(gè)對(duì)應(yīng)類(lèi)型的OOP對(duì)象。在HotSpot中柴底,根據(jù)JVM內(nèi)部使用的對(duì)象業(yè)務(wù)類(lèi)型婿脸,具有多種oopDesc的子類(lèi)。除了oppDesc類(lèi)型外柄驻,opp體系中還有很多instanceOopDesc狐树、arrayOopDesc?等類(lèi)型的實(shí)例,他們都是oopDesc的子類(lèi)鸿脓。
這些OOPS在JVM內(nèi)部有著不同的用途抑钟,例如,instanceOopDesc表示類(lèi)實(shí)例野哭,arrayOopDesc表示數(shù)組在塔。也就是說(shuō),當(dāng)我們使用new創(chuàng)建一個(gè)Java對(duì)象實(shí)例的時(shí)候拨黔,JVM會(huì)創(chuàng)建一個(gè)instanceOopDesc對(duì)象來(lái)表示這個(gè)Java對(duì)象蛔溃。同理,當(dāng)我們使用new創(chuàng)建一個(gè)Java數(shù)組實(shí)例的時(shí)候篱蝇,JVM會(huì)創(chuàng)建一個(gè)arrayOopDesc對(duì)象來(lái)表示這個(gè)數(shù)組對(duì)象贺待。
在HotSpot中,oopDesc類(lèi)定義在oop.hpp中零截,instanceOopDesc定義在instanceOop.hpp中麸塞,arrayOopDesc定義在arrayOop.hpp中。
簡(jiǎn)單看一下相關(guān)定義:
通過(guò)上面的源碼可以看到涧衙,instanceOopDesc實(shí)際上就是繼承了oopDesc哪工,并沒(méi)有增加其他的數(shù)據(jù)結(jié)構(gòu),也就是說(shuō)instanceOopDesc中主要包含以下幾部分?jǐn)?shù)據(jù):markOop _mark和union _metadata?以及一些不同類(lèi)型的?field弧哎。
HotSpot虛擬機(jī)中雁比,對(duì)象在內(nèi)存中存儲(chǔ)的布局可以分為三塊區(qū)域:對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充傻铣。在虛擬機(jī)內(nèi)部章贞,一個(gè)Java對(duì)象對(duì)應(yīng)一個(gè)instanceOopDesc的對(duì)象祥绞。其中對(duì)象頭包含了兩部分內(nèi)容:_mark和_metadata非洲,而實(shí)例數(shù)據(jù)則保存在oopDesc中定義的各種field中。
_mark
文章開(kāi)頭我們就說(shuō)過(guò)蜕径,之所以我們要寫(xiě)這篇文章两踏,是因?yàn)閷?duì)象頭中有和鎖相關(guān)的運(yùn)行時(shí)數(shù)據(jù),這些運(yùn)行時(shí)數(shù)據(jù)是synchronized以及其他類(lèi)型的鎖實(shí)現(xiàn)的重要基礎(chǔ)兜喻,而關(guān)于鎖標(biāo)記梦染、GC分代等信息均保存在_mark中。因?yàn)楸疚闹饕榻B的oop-klass模型,在這里暫時(shí)不對(duì)對(duì)象頭做展開(kāi)帕识,下一篇文章介紹泛粹。
_metadata
前面介紹到的_metadata是一個(gè)共用體,其中_klass是普通指針肮疗,_compressed_klass是壓縮類(lèi)指針晶姊。在深入介紹之前,就要來(lái)到oop-Klass中的另外一個(gè)主角klass了伪货。
klass
klass體系
和oopDesc是其他oop類(lèi)型的父類(lèi)一樣们衙,Klass類(lèi)是其他klass類(lèi)型的父類(lèi)。
Klass向JVM提供兩個(gè)功能:
實(shí)現(xiàn)語(yǔ)言層面的Java類(lèi)(在Klass基類(lèi)中已經(jīng)實(shí)現(xiàn))
實(shí)現(xiàn)Java對(duì)象的分發(fā)功能(由Klass的子類(lèi)提供虛函數(shù)實(shí)現(xiàn))
文章開(kāi)頭的時(shí)候說(shuō)過(guò):之所以設(shè)計(jì)oop-klass模型碱呼,是因?yàn)镠otSopt JVM的設(shè)計(jì)者不想讓每個(gè)對(duì)象中都含有一個(gè)虛函數(shù)表蒙挑。
HotSopt JVM的設(shè)計(jì)者把對(duì)象一拆為二,分為klass和oop愚臀,其中oop的職能主要在于表示對(duì)象的實(shí)例數(shù)據(jù)忆蚀,所以其中不含有任何虛函數(shù)。而klass為了實(shí)現(xiàn)虛函數(shù)多態(tài)懊悯,所以提供了虛函數(shù)表蜓谋。所以,關(guān)于Java的多態(tài)炭分,其實(shí)也有虛函數(shù)的影子在桃焕。
_metadata是一個(gè)共用體,其中_klass是普通指針捧毛,_compressed_klass是壓縮類(lèi)指針观堂。這兩個(gè)指針都指向instanceKlass對(duì)象,它用來(lái)描述對(duì)象的具體類(lèi)型呀忧。
instanceKlass
JVM在運(yùn)行時(shí)师痕,需要一種用來(lái)標(biāo)識(shí)Java內(nèi)部類(lèi)型的機(jī)制。在HotSpot中的解決方案是:為每一個(gè)已加載的Java類(lèi)創(chuàng)建一個(gè)instanceKlass對(duì)象而账,用來(lái)在JVM層表示Java類(lèi)胰坟。
來(lái)看下instanceKlass的內(nèi)部結(jié)構(gòu):
可以看到,一個(gè)類(lèi)該具有的東西泞辐,這里面基本都包含了笔横。
這里還有個(gè)點(diǎn)需要簡(jiǎn)單介紹一下。
在JVM中咐吼,對(duì)象在內(nèi)存中的基本存在形式就是oop吹缔。那么,對(duì)象所屬的類(lèi)锯茄,在JVM中也是一種對(duì)象厢塘,因此它們實(shí)際上也會(huì)被組織成一種oop茶没,即klassOop。同樣的晚碾,對(duì)于klassOop抓半,也有對(duì)應(yīng)的一個(gè)klass來(lái)描述,它就是klassKlass格嘁,也是klass的一個(gè)子類(lèi)琅关。klassKlass作為oop的klass鏈的端點(diǎn)。關(guān)于對(duì)象和數(shù)組的klass鏈大致如下圖:
在這種設(shè)計(jì)下讥蔽,JVM對(duì)內(nèi)存的分配和回收涣易,都可以采用統(tǒng)一的方式來(lái)管理。oop-klass-klassKlass關(guān)系如圖:
內(nèi)存存儲(chǔ)
關(guān)于一個(gè)Java對(duì)象冶伞,他的存儲(chǔ)是怎樣的新症,一般很多人會(huì)回答:對(duì)象存儲(chǔ)在堆上。稍微好一點(diǎn)的人會(huì)回答:對(duì)象存儲(chǔ)在堆上响禽,對(duì)象的引用存儲(chǔ)在棧上徒爹。今天,再給你一個(gè)更加顯得牛逼的回答:
對(duì)象的實(shí)例(instantOopDesc)保存在堆上芋类,對(duì)象的元數(shù)據(jù)(instantKlass)保存在方法區(qū)隆嗅,對(duì)象的引用保存在棧上。
其實(shí)如果細(xì)追究的話(huà)侯繁,上面這句話(huà)有點(diǎn)故意賣(mài)弄的意思胖喳。因?yàn)槲覀兌贾馈?b>方法區(qū)用于存儲(chǔ)虛擬機(jī)加載的類(lèi)信息、常量贮竟、靜態(tài)變量丽焊、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。?所謂加載的類(lèi)信息咕别,其實(shí)不就是給每一個(gè)被加載的類(lèi)都創(chuàng)建了一個(gè) instantKlass對(duì)象么技健。
talk is cheap ,show me the code :
存儲(chǔ)結(jié)構(gòu)如下:
從上圖中可以看到,在方法區(qū)的instantKlass中有一個(gè)int a=1的數(shù)據(jù)存儲(chǔ)惰拱。在堆內(nèi)存中的兩個(gè)對(duì)象的oop中雌贱,分別維護(hù)著int b=3,int b=2的實(shí)例數(shù)據(jù)。和oopDesc一樣偿短,instantKlass也維護(hù)著一些fields欣孤,用來(lái)保存類(lèi)中定義的類(lèi)數(shù)據(jù),比如int a=1翔冀。
總結(jié)
每一個(gè)Java類(lèi)导街,在被JVM加載的時(shí)候披泪,JVM會(huì)給這個(gè)類(lèi)創(chuàng)建一個(gè)instanceKlass纤子,保存在方法區(qū),用來(lái)在JVM層表示該Java類(lèi)。當(dāng)我們?cè)贘ava代碼中控硼,使用new創(chuàng)建一個(gè)對(duì)象的時(shí)候泽论,JVM會(huì)創(chuàng)建一個(gè)instanceOopDesc對(duì)象,這個(gè)對(duì)象中包含了兩部分信息卡乾,對(duì)象頭以及元數(shù)據(jù)翼悴。對(duì)象頭中有一些運(yùn)行時(shí)數(shù)據(jù),其中就包括和多線(xiàn)程相關(guān)的鎖的信息幔妨。元數(shù)據(jù)其實(shí)維護(hù)的是指針鹦赎,指向的是對(duì)象所屬的類(lèi)的instanceKlass。
參考資料
【理解HotSpot虛擬機(jī)】對(duì)象在jvm中的表示:OOP-Klass模型