在《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
有兩個子類,分別爲B
和C
。在main
函數中,b
和c
的靜態類型都是A
,可是在調用callMe()
方法時,JVM會將它們綁定到正確的實現上。這其中奧祕就是JVM的虛分派機制,而該機制的實現用到了Klass中的虛函數表。.net
跟Oop同樣,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)主要是爲了實現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
,進而獲得方法的實際地址,最後根據該地址找到方法的字節碼並執行。
這兩篇文章咱們探討了HotSpot的Oop-Klass對象模型,其中Oop表示對象的實例,存儲在堆中;Klass表示對象的類型,存儲在方法區中。但這也只是講述了Oop-Klass對象模型中最基礎的部分,該模型所包含的內容還遠不止這些,若是想要更加全面而深刻地瞭解Oop-Klass對象模型,最好的方法是閱讀HotSpot的源碼。
最後,咱們經過一張圖來總結這兩篇文章所講述的內容: