1. 程式人生 > >Java對象模型

Java對象模型

方法 efk 對象模型 ted 列表 ack instance mode round

java對象

在內存中,一個Java對象包含三部分:對象頭、實例數據和對齊填充。而對象頭中又包含鎖狀態標誌、線程持有的鎖等標誌。

oop-klass model

OOP(Ordinary Object Pointer)指的是普通對象指針,而Klass用來描述對象實例的具體類型。

oop體系:

//定義了oops共同基類
typedef class   oopDesc*                            oop;
//表示一個Java類型實例
typedef class   instanceOopDesc*            instanceOop;
//表示一個Java方法
typedef class
methodOopDesc* methodOop; //表示一個Java方法中的不變信息 typedef class constMethodOopDesc* constMethodOop; //記錄性能信息的數據結構 typedef class methodDataOopDesc* methodDataOop; //定義了數組OOPS的抽象基類 typedef class arrayOopDesc* arrayOop; //表示持有一個OOPS數組 typedef class
objArrayOopDesc* objArrayOop; //表示容納基本類型的數組 typedef class typeArrayOopDesc* typeArrayOop; //表示在Class文件中描述的常量池 typedef class constantPoolOopDesc* constantPoolOop; //常量池告訴緩存 typedef class constantPoolCacheOopDesc* constantPoolCacheOop; //描述一個與Java類對等的C++類 typedef class
klassOopDesc* klassOop; //表示對象頭 typedef class markOopDesc* markOop;

如上面代碼所示, oops模塊包含多個子模塊, 每個子模塊對應一個類型, 每一個類型的oop都代表一個在JVM內部使用的特定對象的類型。其中有一個變量oop的類型oopDesc是oops模塊的共同基類型。而oopDesc類型又包含instanceOopDesc (類實例)、arrayOopDesc (數組)等子類類型。其中instanceOopDesc 中主要包含以下幾部分數據:markOop _markunion _metadata 以及一些不同類型的 field

在java程序運行過程中, 每創建一個新的java對象, 在JVM內部就會相應的創建一個對應類型的oop對象來表示該java對象。而在HotSpot虛擬機中, 對象在內存中包含三塊區域: 對象頭、實例數據和對齊填充。其中對象頭包含兩部分內容:_mark_metadata,而實例數據則保存在oopDesc中定義的各種field中。

_mark:

_mark這一部分用於存儲對象自身的運行時數據, 如哈希碼、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等, 這部分數據的長度在32位和64位的虛擬機(未開啟壓縮指針)中分別為32bit和64bit, 官方稱它為 "Mark Word"。對象需要存儲的運行時數據很多, 其實已經超出了32位和64位Bitmap結構所能記錄的限度, 但是對象頭信息是與對象自身定義的數據無關的額外存儲成本, 考慮到虛擬機的空間效率, Mark Word被設計成一個非固定的數據結構以便在極小的空間內存儲盡量多的信息, 它會根據對象的狀態復用自己的存儲空間。

_metadata:

_metadata這一部分是類型指針, 即對象指向它的類元數據的指針, 虛擬機通過這個指針來確定這個對象是哪個類的實例。並不是所有的虛擬機實現都必須在對象數據上保留類型指針, 換句話說查找對象的元數據信息並不一定要經過對象本身, 其取決於虛擬機實現的對象訪問方式。目前主流的訪問方式有使用句柄和直接指針兩種, 兩者方式的不同這裏先暫不做介紹。另外, 如果對象是一個Java數組, 那麽在對象頭中還必須有一塊用於記錄數組長度的數據, 因為虛擬機可以通過普通java對象的元數據信息確定java對象的大小, 但是從數組的元數據中卻無法確定數組的大小。

klass

Klass體系:

//klassOop的一部分,用來描述語言層的類型
class  Klass;
//在虛擬機層面描述一個Java類
class   instanceKlass;
//專有instantKlass,表示java.lang.Class的Klass
class     instanceMirrorKlass;
//專有instantKlass,表示java.lang.ref.Reference的子類的Klass
class     instanceRefKlass;
//表示methodOop的Klass
class   methodKlass;
//表示constMethodOop的Klass
class   constMethodKlass;
//表示methodDataOop的Klass
class   methodDataKlass;
//作為klass鏈的端點,klassKlass的Klass就是它自身
class   klassKlass;
//表示instanceKlass的Klass
class     instanceKlassKlass;
//表示arrayKlass的Klass
class     arrayKlassKlass;
//表示objArrayKlass的Klass
class       objArrayKlassKlass;
//表示typeArrayKlass的Klass
class       typeArrayKlassKlass;
//表示array類型的抽象基類
class   arrayKlass;
//表示objArrayOop的Klass
class     objArrayKlass;
//表示typeArrayOop的Klass
class     typeArrayKlass;
//表示constantPoolOop的Klass
class   constantPoolKlass;
//表示constantPoolCacheOop的Klass
class   constantPoolCacheKlass;

和oopDesc是其他oop類型的父類一樣,Klass類是其他klass類型的父類。

Klass向JVM提供兩個功能:

  • 實現語言層面的Java類(在Klass基類中已經實現)
  • 實現Java對象的分發功能(由Klass的子類提供虛函數實現)

HotSpot JVM的設計者因為不想讓每一個對象中都含有一個虛函數表, 所以設計了oop-klass模型, 將對象一分為二, 分為klassoop。其中oop主要用於表示對象的實例數據, 所以不含有任何虛函數。而klass為了實現虛函數多態, 所以提供了虛函數表。所以,關於Java的多態,其實也有c++虛函數的影子在。

InstanceClass

JVM在運行時,需要一種用來標識Java內部類型的機制。在HotSpot中的解決方案是:為每一個已加載的Java類創建一個InstanceClass對象,用來在JVM層表示Java類。

InstanceClass內部結構:

//類擁有的方法列表
  objArrayOop     _methods;
  //描述方法順序
  typeArrayOop    _method_ordering;
  //實現的接口
  objArrayOop     _local_interfaces;
  //繼承的接口
  objArrayOop     _transitive_interfaces;
  //
  typeArrayOop    _fields;
  //常量
  constantPoolOop _constants;
  //類加載器
  oop             _class_loader;
  //protected域
  oop             _protection_domain;
      ....

在JVM中,對象在內存中的基本存在形式就是oop。那麽,對象所屬的類,在JVM中也是一種對象,因此它們實際上也會被組織成一種oop,即klassOop。同樣的,對於klassOop,也有對應的一個klass來描述,它就是klassKlass,也是klass的一個子類。klassKlass作為oop的klass鏈的端點, 它的klass就是它自身。

內存存儲

我們首先來看看下面這段代碼的存儲結構。

class Model
{
    public static int a = 1;
    public int b;

    public Model(int b) {
        this.b = b;
    }
}

public static void main(String[] args) {
    int c = 10;
    Model modelA = new Model(2);
    Model modelB = new Model(3);
}

存儲結構如下:

技術分享圖片

由此我們能得出結論: 對象的實例(instantOopDesc)保存在堆上,對象的元數據(instantKlass)保存在方法區,對象的引用保存在棧上。

小結

在JVM加載java類的時候, JVM會給這個類創建一個instanceKlass並保存在方法區, 用來在JVM層表示該java類。當我們使用new關鍵字創建一個對象時, JVM會創建一個instanceOopDesc對象, 這個對象包含了對象頭和元數據兩部分信息。對象頭中有一些運行時數據, 其中就包括和多線程有關的鎖的信息。而元數據維護的則是指向對象所屬的類的InstanceKlass的指針。

參考資料

深入理解多線程(二)—— Java的對象模型-HollisChuang‘s Blog

深入理解多線程(三)—— Java的對象頭-HollisChuang‘s Blog

Java對象模型