Java的對象模型——Oop-Klass模型(二)

前言

《Java對象表示——Oop-Klass模型(一)》一文的最後講到,爲了實現Java方法調用的動態綁定,HotSpot使用了與C++虛函數相似的機制,同時爲了不每一個對象都維護一個虛函數表,因而就設計了Klass類。java

以下爲HotSpot源碼中對Klass的功能介紹:web

A Klass provides:數組

​ 1: language level class object (method dictionary etc.)編輯器

​ 2: provide vm dispatch behavior for the objectide

Both functions are combined into one C++ class.函數

可見,Klass主要提供了兩個功能:oop

(1)用於表示Java類。Klass中保存了一個Java對象的類型信息,包括類名、限定符、常量池、方法字典等。一個class文件被JVM加載以後,就會被解析成一個Klass對象存儲在內存中。this

(2)實現對象的虛分派(virtual dispatch)。所謂的虛分派,是JVM用來實現多態的一種機制。spa

class A {
    void callMe() {
      System.out.println("This is A.");
    }
}
class B extends A {
    @Override
    public void callMe() {
        System.out.println("This is B.");
    }
}
class C extends A {
    @Override
    public void callMe() {
        System.out.println("This is C.");
    }
}
public class VmDispatch {
    public static void main(String[] args) {
        A b = new B();
        A c = new C();
        // b和c的靜態類型爲A,那麼JVM是如何將它們動態綁定到正確的實現上的呢?
        b.callMe();
        c.callMe();
    }
}
/* Output:
This is B.
This is C.
*/
複製代碼

考慮上述例子,基類A有兩個子類,分別爲BC。在main函數中,bc的靜態類型都是A,可是在調用callMe()方法時,JVM會將它們綁定到正確的實現上。這其中奧祕就是JVM的虛分派機制,而該機制的實現用到了Klass中的虛函數表.net

Klass的繼承體系

跟Oop同樣,Klass也有一個繼承體系,以下圖所示:

Klass繼承體系
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數組類
class     ObjArrayKlass;  // 普通對象的數組類
class     TypeArrayKlass;  // 基礎類型的數組類
...
複製代碼

不一樣於Oop,Klass在InstanceKlass下又設計了3個子類,其中InstanceMirrorKlass用於表示java.lang.Class類型,該類型對應的oop特別之處在於其包含了static field,所以計算oop大小時須要把static field也考慮進來;InstanceClassLoaderKlass主要提供了遍歷當前ClassLoader的繼承體系;InstanceRefKlass用於表示java.lang.ref.Reference及其子類。

對象的類型信息

Klass的主要用途之一就是保存一個Java對象的類型信息,以下選出其中一些比較重要的field:

// hotspot/src/share/vm/oops/klass.hpp
class Klass : public Metadata {
...
  // 類名,其中普通類名和數組類名略有不一樣
  // 普通類名如:java/lang/String,數組類名如:[Ljava/lang/String;
  Symbol*     _name;
  // 最後一個secondary supertype
  Klass*      _secondary_super_cache;
  // 保存全部secondary supertypes
  Array<Klass*>* _secondary_supers;
  // 保存全部primary supertypes的有序列表
  Klass*      _primary_supers[_primary_super_limit];
  // 當前類所屬的java/lang/Class對象對應的oop
  oop       _java_mirror;
  // 當前類的直接父類
  Klass*      _super;
  // 第一個子類 (NULL if none); _subklass->next_sibling() 爲下一個
  Klass*      _subklass;
  // 串聯起當前類全部的子類
  Klass*      _next_sibling;
  // 串聯起被同一個ClassLoader加載的全部類(包括當前類)
  Klass*      _next_link;
  // 對應用於加載當前類的java.lang.ClassLoader對象
  ClassLoaderData* _class_loader_data;
  // 提供訪問當前類的限定符途徑, 主要用於Class.getModifiers()方法.
  jint        _modifier_flags;
  // 訪問限定符
  AccessFlags _access_flags;    
...
}
複製代碼

如上述代碼片斷所示,Klass繼承了Metadata,後者爲《深刻解析Java的運行時數據區》一文中提到的「元空間」(Metaspace)的實現,這也意味着Java對象的類型信息存儲在方法區,而不是在堆中

primary supertype和secondary supertype主要用於快速類型檢查(好比在調用instanceOf時可以快速獲得結果),其中primary type和secondary type的定義出如今《Fast subtype checking in the HotSpot JVM》一文中:

A klass T is a primary type iff T is a proper class, or an array of a primary type, or an array of primitive values. Interfaces and arrays of interfaces are excluded.

A klass T is a secondary type iff T is a interface or an array of a secondary type. Every type is either a primary type or a secondary type but not both.

接着,咱們繼續看下錶示普通對象類型的InstanceKlass所包含的信息,它繼承自Klass,在父類的基礎上增長了很多信息,以下列出較爲重要的一些:

// hotspot/src/share/vm/oops/instanceKlass.hpp
class InstanceKlass: public Klass {
...
  // 當前類的狀態
  enum ClassState {
    allocated,  // 已分配
    loaded,  // 已加載,並添加到類的繼承體系中
    linked,  // 連接/驗證完成
    being_initialized,  // 正在初始化
    fully_initialized,  // 初始化完成
    initialization_error  // 初始化失敗
  };
  // 當前類的註解
  Annotations*    _annotations;
  // 當前類數組中持有的類型
  Klass*          _array_klasses;
  // 當前類的常量池
  ConstantPool* _constants;
  // 當前類的內部類信息
  Array<jushort>* _inner_classes;
  // 保存當前類的全部方法.
  Array<Method*>* _methods;
  // 若是當前類實現了接口,則保存該接口的default方法
  Array<Method*>* _default_methods;
  // 保存當前類全部方法的位置信息
  Array<int>*     _method_ordering;
  // 保存當前類全部default方法在虛函數表中的位置信息
  Array<int>*     _default_vtable_indices;
  // 保存當前類的field信息(包括static field),數組結構爲:
  // f1: [access, name index, sig index, initial value index, low_offset, high_offset]
  // f2: [access, name index, sig index, initial value index, low_offset, high_offset]
  //      ...
  // fn: [access, name index, sig index, initial value index, low_offset, high_offset]
  //     [generic signature index]
  //     [generic signature index]
  //     ...
  Array<u2>*      _fields;
...
}
複製代碼

注意到,_fields中的每一個元素都包含了當前field都偏移量信息,如前文《Java對象表示——Oop-Klass模型(一)》所提到,這些偏移量用於在oop中找到對應field的地址。

虛函數表(vtable)

虛函數表(vtable)主要是爲了實現Java中的虛分派功能而存在。HotSpot把Java中的方法都抽象成了Method對象,InstanceKlass中的成員屬性_methods就保存了當前類全部方法對應的Method實例。HotSpot並無顯式地把虛函數表設計爲Klass的field,而是提供了一個虛函數表視圖,並在類初始化時建立出來。

// hotspot/src/share/vm/oops/instanceKlass.hpp
class InstanceKlass: public Klass {
...  
  // 返回一個新的vtable,在類初始化時建立
  klassVtable* vtable() const;
  inline Method* method_at_vtable(int index);
..
}
// 如下爲方法對應實現
// hotspot/src/share/vm/oops/instanceKlass.cpp
...
// vtable()的實現
klassVtable* InstanceKlass::vtable() const {
  return new klassVtable(this, start_of_vtable(), vtable_length() / vtableEntry::size());
}
// method_at_vtable()的實現
inline Method* InstanceKlass::method_at_vtable(int index)  {
  ... // 校驗邏輯
  vtableEntry* ve = (vtableEntry*)start_of_vtable();
  return ve[index].method();
}
複製代碼

一個klassVtable可當作是由多個vtableEntry組成的數組,其中每一個元素vtableEntry裏面都包含了一個方法的地址。在進行虛分派時,JVM會根據方法在klassVtable中的索引,找到對應的vtableEntry,進而獲得方法的實際地址,最後根據該地址找到方法的字節碼並執行。

vtalbe結構
vtalbe結構

總結

這兩篇文章咱們探討了HotSpot的Oop-Klass對象模型,其中Oop表示對象的實例,存儲在堆中;Klass表示對象的類型,存儲在方法區中。但這也只是講述了Oop-Klass對象模型中最基礎的部分,該模型所包含的內容還遠不止這些,若是想要更加全面而深刻地瞭解Oop-Klass對象模型,最好的方法是閱讀HotSpot的源碼。

最後,咱們經過一張圖來總結這兩篇文章所講述的內容:

Oop-Klass對象模型在內存中的分佈
Oop-Klass對象模型在內存中的分佈
相關文章
相關標籤/搜索