談起Java對象,筆者的第一反應是在:Java中的每個對象(不包括基礎類型)都繼承於Object
對象。相信這也是大多數程序員對Java對象的初次印象,Object
能夠表示全部的Java對象。可是,這種理解僅僅是停留在語言層面,至於更深的JVM層面,對象仍是用Object
來表示嗎?顯然不是。JVM一般使用非Java語言實現,是用來解析並運行Java程序的,它有本身的模型來表示Java語言的各類特性,包括Object
。下面咱們以HotSpot爲例,一塊兒來探討Java對象在JVM層面的Java對象模型。程序員
HotSpot採用C++語言實現,下文中的JVM如無特殊說明,指的都是HotSpot。web
Java程序經過new
操做符來建立一個對象,在深刻探討HotSpot的Java對象模型前,咱們先看下new
操做符的具體實現。數組
上述代碼片斷來自HotSpot源碼中new
操做符的實現函數,先不深刻分析每一行的具體含義,這段代碼給咱們最直觀的功能就是:先對klass
對象進行初始化工做,而後再用它來建立出oop
對象。到這裏咱們大體就能猜出,oop
表示的就是一個Java對象。而這裏的klass
和Java中的Class
之間彷佛有着緊密的聯繫,一是二者的名字很是相似,另外也可經過第16行代碼獲得進一步的確定。對Java的反射機制稍微有所瞭解的人,看着第16行代碼必定很熟悉,由於它與使用Class.newInstance()
方法來建立Object
對象很相似。多線程
實際正如上述所猜想,HotSpot使用Oop-Klass模型來表示Java的對象。編輯器
這裏的Oop並不是是Object-oriented programming,而是Ordinary object pointer(普通對象指針),是HotSpot用來表示Java對象的實例信息的一個體系。其中oop
是Oop體系中的最高父類,整個繼承體系以下所示:函數
每一個Java對象都有它獨有的生命週期,咱們使用new
操做符將它建立出來,而後對它執行各式各樣的操做(如獲取成員屬性、調用成員函數、加鎖等),最後被GC回收掉。那麼Java對象的這一系列經歷,JVM又是怎麼實現的呢?JVM使用Oop來表示一個Java對象,天然地,這些經歷都會跟oop
有關。oop
oop
的子類有兩個,分別是instanceOop
和arrayOop
。前者表示Java中普通的對象,後者則表示數組對象。arrayOop
也有兩個子類,objArrayOop
表示普通對象類型的數組,而typeArrayOopDesc
則表示基礎類型的數組。以下圖所示,oop
的存儲結構主要由對象頭和對象體組成。性能
oop
主要有兩個成員屬性:優化
_mark
和_metadata
被稱爲對象頭,其中前者存儲對象的運行時記錄信息;後者是一個指針,指向當前對象所屬的Klass
對象。spa
由於某些歷史緣由,HotSpot把
markOop
放到Oop體系裏,可是它並繼承oop
,所以前文所描述的Oop體系並無包含它。
markOop
的存儲結構在32位和64位系統中有所差別,可是具體存儲的信息是同樣的,本節只介紹它在32位系統中的存儲結構。在32位系統中,markOop
一共佔32位,存儲結構如圖下所示:
從圖中可知,諸如對象hash值、線程ID、分代年齡等信息都是存儲在markOop
中,並且在不一樣的狀態下,其結構也是略有不一樣。無鎖指一個對象沒有被加鎖時的狀態;偏向鎖,顧名思義會偏向於第一個訪問鎖的線程,當同步鎖只有一個線程訪問時,JVM會將其優化爲偏向鎖,此時就至關於沒有同步語義;當發生多線程競爭時,偏向鎖就會膨脹爲輕量級鎖,後者採用CAS(Compare And Swap)實現,避免了用戶態和內核態之間的切換;若是某個線程獲取輕量級鎖失敗,該鎖就會繼續膨脹爲重量級鎖,此時JVM會向操做系統申請互斥量,所以性能消耗也是最高的。
oop
提供4個方法來判斷當前對象處於何種狀態下:
從上述代碼可知,oop
調用markOop
的方法來判斷當前對象是否已經加鎖、是不是偏向鎖,markOop
則經過判斷其存儲結構中的標誌位來實現,以下列代碼所示:
JVM將Java對象的field存儲在oop
的對象體中,oop
提供了一系列的方法來獲取和設置field,而且針對每種基礎類型都提供了特有的實現。
具體實現以下列代碼所示:
由上述代碼片斷可知,每一個field在oop
中都有一個對應的偏移量(offset),oop
經過該偏移量獲得該field的地址,再根據地址獲得具體數據。所以,Java對象中的field存儲的並非對象自己,而是對象的地址。
HotSpot採用Oop-Klass模型來表示Java對象,其中Klass對應着Java對象的類型(Class),而Oop則對應着Java對象的實例(Instance)。Oop是一個繼承體系,其中oop
是體系中的最高父類,它的存儲結構能夠分紅對象頭和對象體。對象頭存儲的是對象的一些元數據,對象體存儲的是具體的成員屬性。值得注意的是,若是成員屬性屬於普通對象類型,則oop
只存儲它的地址。
咱們都知道Java中的普通方法(沒有static和final修飾)是動態綁定的,在C++中,動態綁定經過虛函數來實現,代價是每一個C++對象都必須維護一張虛函數表。Java的特色就是一切皆是對象,若是每一個對象都維護一張虛函數表,內存開銷將會很是大。JVM對此作了優化,虛函數表再也不由每一個對象維護,改爲由Class類型維護,全部屬於該類型的對象共用一張虛函數表。所以咱們並無在oop
上找到方法調用的相關邏輯,這部分的代碼被放在了klass
裏面。
Klass相關的內容將會在下一篇文章《Java的對象模型——Oop-Klass(二)》中介紹。