上一篇 HotSpot源碼分析之類模型 介紹了類模型的基礎類Klass的重要屬性及方法,這一篇介紹一下InstanceKlass及InstanceKlass的子類。
1、InstanceKlass類
每個InstanceKlass對象表示一個具體的Java類(這里的Java類不包括Java數組)。InstanceKlass類及重要屬性的定義如下:
class InstanceKlass: public Klass {
...
protected:
// Annotations for this class
Annotations* _annotations;
// Array classes holding elements of this class.
Klass* _array_klasses;
// Constant pool for this class.
ConstantPool* _constants;
// The InnerClasses attribute and EnclosingMethod attribute. The
// _inner_classes is an array of shorts. If the class has InnerClasses
// attribute, then the _inner_classes array begins with 4-tuples of shorts
// [inner_class_info_index, outer_class_info_index,
// inner_name_index, inner_class_access_flags] for the InnerClasses
// attribute. If the EnclosingMethod attribute exists, it occupies the
// last two shorts [class_index, method_index] of the array. If only
// the InnerClasses attribute exists, the _inner_classes array length is
// number_of_inner_classes * 4. If the class has both InnerClasses
// and EnclosingMethod attributes the _inner_classes array length is
// number_of_inner_classes * 4 + enclosing_method_attribute_size.
Array<jushort>* _inner_classes;
// Array name derived from this class which needs unreferencing
// if this class is unloaded.
Symbol* _array_name;
// Number of heapOopSize words used by non-static fields in this klass
// (including inherited fields but after header_size()).
int _nonstatic_field_size;
int _static_field_size; // number words used by static fields (oop and non-oop) in this klass
// Constant pool index to the utf8 entry of the Generic signature,
// or 0 if none.
u2 _generic_signature_index;
// Constant pool index to the utf8 entry for the name of source file
// containing this klass, 0 if not specified.
u2 _source_file_name_index;
u2 _static_oop_field_count;// number of static oop fields in this klass
u2 _java_fields_count; // The number of declared Java fields
int _nonstatic_oop_map_size;// size in words of nonstatic oop map blocks
u2 _minor_version; // minor version number of class file
u2 _major_version; // major version number of class file
Thread* _init_thread; // Pointer to current thread doing initialization (to handle recusive initialization)
int _vtable_len; // length of Java vtable (in words)
int _itable_len; // length of Java itable (in words)
OopMapCache* volatile _oop_map_cache; // OopMapCache for all methods in the klass (allocated lazily)
JNIid* _jni_ids; // First JNI identifier for static fields in this class
jmethodID* _methods_jmethod_ids; // jmethodIDs corresponding to method_idnum, or NULL if none
nmethodBucket* _dependencies; // list of dependent nmethods
nmethod* _osr_nmethods_head; // Head of list of on-stack replacement nmethods for this class
// Class states are defined as ClassState (see above).
// Place the _init_state here to utilize the unused 2-byte after
// _idnum_allocated_count.
u1 _init_state; // state of class
u1 _reference_type; // reference type
// Method array.
Array<Method*>* _methods;
// Default Method Array, concrete methods inherited from interfaces
Array<Method*>* _default_methods;
// Interface (Klass*s) this class declares locally to implement.
Array<Klass*>* _local_interfaces;
// Interface (Klass*s) this class implements transitively.
Array<Klass*>* _transitive_interfaces;
// Int array containing the vtable_indices for default_methods
// offset matches _default_methods offset
Array<int>* _default_vtable_indices;
// Instance and static variable information, starts with 6-tuples of shorts
// [access, name index, sig index, initval index, low_offset, high_offset]
// for all fields, followed by the generic signature data at the end of
// the array. Only fields with generic signature attributes have the generic
// signature data set in the array. The fields array looks like following:
//
// 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;
// embedded Java vtable follows here
// embedded Java itables follows here
// embedded static fields follows here
// embedded nonstatic oop-map blocks follows here
// embedded implementor of this interface follows here
// The embedded implementor only exists if the current klass is an
// iterface. The possible values of the implementor fall into following
// three cases:
// NULL: no implementor.
// A Klass* that's not itself: one implementor.
// Itsef: more than one implementors.
// embedded host klass follows here
// The embedded host klass only exists in an anonymous class for
// dynamic language support (JSR 292 enabled). The host class grants
// its access privileges to this class also. The host class is either
// named, or a previously loaded anonymous class. A non-anonymous class
// or an anonymous class loaded through normal classloading does not
// have this embedded field.
...
}
重要屬性的介紹如下表所示。
| 字段名 | 作用 |
| _annotations | Annotations類型的指針,保存該類使用的所有注解 |
| _array_klasses | 數組元素為該類的數組Klass指針,例如ObjArrayKlass是對象數組且元素類型為Object, 那么表示Object類的InstanceKlass對象的_array_klasses就是指向ObjArrayKlass對象的指針 |
| _array_name | 以該類為數組元素的數組的名字,如果當前InstanceKlass對象表示Object類,則名稱為"[Ljava/lang/Object;" |
| _constants | ConstantPool類型的指針,用來指向保存了Java類的常量池信息的ConstantPool對象 |
| _inner_classes | 用一個jushort數組保存當前類的InnerClasses屬性和EnclosingMethod屬性 |
| _nonstatic_field_size | 非靜態字段需要占用的內存大小 ,以字為單位。在為當前類表示的Java類所創建的對象(使用oop表示)分配內存時, 會參考此屬性的值分配內存,這個值在類文件解析時會計算好。 |
| _static_field_size | 靜態字段需要占用的內存大小 ,以字為單位。在為當前類表示的Java類所創建的java.lang.Class對象(使用oop表示)分配內存時, 會參考此屬性的值分配內存,這個值在類文件解析時會計算好。 |
| _generic_signature_index | 保存此類的簽名在常量池中的索引 |
| _source_file_name_index | 保存此類的源文件名在常量池中的索引 |
| _static_oop_field_count | 此類包含的靜態引用類型字段的數量 |
| _java_fields_count | 此類包含的字段總數量 |
| _nonstatic_oop_map_size | 非靜態的oop map block需要占用的內存大小,以字為單位 |
| _minor_version | 類的次版本號 |
| _major_version | 類的主版本號 |
| _init_thread | 執行此類初始化的Thread指針 |
| _vtable_len | Java虛函數表(vtable)所占用的內存大小,以字為單位 |
| _itable_len | Java接口函數表(itable)所占用的內存大小,以字為單位 |
| _oop_map_cache | OopMapCache指針,該類的所有方法的OopMapCache |
| _jni_ids/_methods_jmethod_ids | JNIid指針與jmethodID指針,這2個指針對於JNI方法操作屬性和方法非常重要,在介紹JNI時會詳細介紹。 |
| _dependencies | nmethodBucket指針,依賴的本地方法,以根據其_next屬性獲取下一個nmethod |
| _osr_nmethods_head | 棧上替換的本地方法鏈表的頭元素 |
| _init_state | 表示類的狀態,為枚舉類型ClassState,定義了如下常量值:
|
| _reference_type | 引用類型,可能是強引用、軟引用、弱引用等 |
| _methods | 保存方法的指針數組 |
| _default_methods | 保存方法的指針數組,從接口繼承的默認方法 |
| _local_interfaces | 保存接口的指針數組,直接實現的接口Klass |
| _transitive_interfaces | 保存接口的指針數組,包含_local_interfaces和間接實現的接口 |
| _default_vtable_indices | 默認方法在虛函數表中的索引 |
| _fields | 類的字段屬性,每個字段的6個屬性access,、name index、sig index、initial value index、low_offset、high_offset組成一個元組, access表示訪問控制屬性,根據name index可以獲取屬性名,根據initial value index可以獲取初始值,根據low_offset與 high_offset可以獲取該屬性在內存中的偏移量。另外保存完所有屬性之后還可能會保存泛型簽名信息。 |
有了InstanceKlass與Klass中定義的這些屬性足夠用來保存Java類元信息。在后續的類解析中會看到對相關變量的屬性填充操作。除了保存類元信息外,此類還有另外一個重要的功能,即支持方法分派,主要是通過Java虛函數表和Java接口函數表來完成的,不過C++並不像Java一樣,保存信息時非要在類中定義出相關屬性,C++只是在分配內存時為要存儲的信息分配好特定的內存,然后直接通過內存偏移來操作即可。
接下來幾個屬性是沒有對應的屬性名,只能通過指針和偏移量的方式訪問:
- Java vtable:Java虛函數表,大小等於_vtable_len;
- Java itables:Java接口函數表,大小等於 _itable_len;
- 非靜態oop-map blocks ,大小等於_nonstatic_oop_map_size。GC在垃圾回收時,遍歷某個對象所引用的其它對象時,會結合此信息進行查找;
- 接口的實現類,只有當前類表示一個接口時存在。如果接口沒有任何實現類則為NULL;如果只有一個實現類則為該實現類的Klass指針;如果有多個實現類,為當前類本身;
- host klass,只在匿名類中存在,為了支持JSR 292中的動態語言特性,會給匿名類生成一個host klass。
HotSpot在解析一個類時會調用InstanceKlass::allocate_instance_klass()方法分配內存,而分配多大的內存則是通過調用InstanceKlass::size()計算出來的,調用語句如下:
int size = InstanceKlass::size(vtable_len,itable_len,nonstatic_oop_map_size,isinterf,is_anonymous);
調用的size()方法的實現如下:
static int size(
int vtable_length,
int itable_length,
int nonstatic_oop_map_size,
bool is_interface,
bool is_anonymous
){
return align_object_size(header_size() + // InstanceKlass類本身占用的內存大小
align_object_offset(vtable_length) +
align_object_offset(itable_length) +
// [EMBEDDED nonstatic oop-map blocks] size in words = nonstatic_oop_map_size
// The embedded nonstatic oop-map blocks are short pairs (offset, length)
// indicating where oops are located in instances of this klass.
(
(is_interface || is_anonymous) ?
align_object_offset(nonstatic_oop_map_size) :
nonstatic_oop_map_size
) +
// [EMBEDDED implementor of the interface] only exist for interface
(
is_interface ? (int)sizeof(Klass*)/HeapWordSize : 0
) +
// [EMBEDDED host klass ] only exist for an anonymous class (JSR 292 enabled)
(
is_anonymous ? (int)sizeof(Klass*)/HeapWordSize : 0)
);
}
方法返回值就是此次創建Klass對象所需要開辟的內存大小。由此方法的計算邏輯可以看出Klass對象的內存布局情況。
圖中的灰色陰影部分是可選部分。關於vtable_length和itable_length以及nonstatic_oop_map_size的值在類解析的過程中會計算好,在后續介紹類解析過程中會詳細介紹。
調用的header_size()方法就是計算此類的對象所占用的內存大小,實現如下:
// Sizing (in words)
static int header_size(){
return align_object_offset(sizeof(InstanceKlass)/HeapWordSize); // 以HeapWordSize為單位,64位一個字為8字節,所以值為8
}
調用的align_object_offset()方法是進行內存對齊,這是一塊非常重要的C++知識點,后面會專門進行講解。
2、InstanceKlass類的子類
InstanceKlass共有3個直接子類,這3個子類用來表示一些特殊的類,下面簡單介紹一下這3個子類:
(1)InstanceRefKlass
java/lang/ref/Reference的子類需要使用InstanceRefKlass類來表示,在創建這個類的實例時,_reference_type字段的值通常會說明當前的類表示的是哪種引用類型。取值已經在枚舉類中定義,如下:
REF_NONE枚舉常量的定義如下:
// ReferenceType is used to distinguish between java/lang/ref/Reference subclasses
enum ReferenceType {
REF_NONE, // Regular class
REF_OTHER, // Subclass of java/lang/ref/Reference, but not subclass of one of the classes below
REF_SOFT, // Subclass of java/lang/ref/SoftReference
REF_WEAK, // Subclass of java/lang/ref/WeakReference
REF_FINAL, // Subclass of java/lang/ref/FinalReference
REF_PHANTOM // Subclass of java/lang/ref/PhantomReference
};
可以看到,所有的Java類Reference及子類都會用C++類InstanceRefKlass的對象來表示。當無法判斷到底是哪個Java子類時,會將_reference_type的值設置為REF_OTHER。
因為這些類需要垃圾回收器特殊處理 ,在后續講解強引用、弱引用、虛引用以及幽靈引用時在詳細介紹。
(2)InstanceMirrorKlass類
InstanceMirrorKlass對象用於表示特殊的java.lang.Class類,增加了一個靜態屬性_offset_of_static_fields,用來描述靜態字段的起始偏移量。定義如下:
static int _offset_of_static_fields;
只所以增加這個屬性,是由於java.lang.Class類比較特殊。正常情況下,HotSpot使用Klass來表示Java類,用oop來表示Java對象,而Java對象中可能定義靜態或非靜態字段,非靜態字段值存儲在oop中,而靜態字段值存儲在表示當前Java類的java.lang.Class對象中。java.lang.Class類用InstanceMirrorKlass對象來表示,java.lang.Class對象用oop來表示,那么Class對象的非靜態字段值存儲在oop中,而Class類自身也定義了靜態字段,那么這些值同樣存儲在了Class對象中,也就是表示Class對象的oop中,這樣靜態與非靜態字段存儲在了一個oop上,通過_offset_of_static_fields屬性偏移來定位靜態字段的存儲位置。
該屬性是通過init_offset_of_static_fields方法初始化的,其初始化的過程如下:
static void init_offset_of_static_fields() {
// Cache the offset of the static fields in the Class instance
assert(_offset_of_static_fields == 0, "once");
// java.lang.Class類使用InstanceMirrorKlass對象來表示,而java.lang.Class對象通過Oop對象來表示,那么imk->size_helper()獲取的就是
// Oop對象的大小,左移3位將字轉換為字節。緊要着Oop對象后存儲靜態字段的值
InstanceMirrorKlass* imk = InstanceMirrorKlass::cast(SystemDictionary::Class_klass());
_offset_of_static_fields = imk->size_helper() << LogHeapWordSize; // LogHeapWordSize=3
}
int size_helper() const {
return layout_helper_to_size_helper(layout_helper());
}
static int layout_helper_to_size_helper(jint lh) {
assert(lh > (jint)_lh_neutral_value, "must be instance");
return lh >> LogHeapWordSize;
}
int layout_helper() const{ return _layout_helper; }
調用java.lang.Class類(通過InstanceMirrorKlass對象來表示)的size_helper()方法來獲取java.lang.Class對象(通過Oop對象來表示)的大小,這個大小是java.lang.Class類中本身聲明的一些屬性需要占用的大小,緊隨其后的就是靜態存儲的區域。
打開命令-XX:+PrintFieldLayout后的打印結果如下:
非靜態的布局如下:
java.lang.Class: field layout @ 12 --- instance fields start --- @ 12 "cachedConstructor" Ljava.lang.reflect.Constructor; @ 16 "newInstanceCallerCache" Ljava.lang.Class; @ 20 "name" Ljava.lang.String; @ 24 "reflectionData" Ljava.lang.ref.SoftReference; @ 28 "genericInfo" Lsun.reflect.generics.repository.ClassRepository; @ 32 "enumConstants" [Ljava.lang.Object; @ 36 "enumConstantDirectory" Ljava.util.Map; @ 40 "annotationData" Ljava.lang.Class$AnnotationData; @ 44 "annotationType" Lsun.reflect.annotation.AnnotationType; @ 48 "classValueMap" Ljava.lang.ClassValue$ClassValueMap; @ 52 "protection_domain" Ljava.lang.Object; @ 56 "init_lock" Ljava.lang.Object; @ 60 "signers_name" Ljava.lang.Object; @ 64 "klass" J @ 72 "array_klass" J @ 80 "classRedefinedCount" I @ 84 "oop_size" I @ 88 "static_oop_field_count" I @ 92 --- instance fields end --- @ 96 --- instance ends ---
這就是java.lang.Class非靜態字段的布局,在類解析過程中已經計算好了各個字段的偏移量。在完成非靜態字段布局后,緊接着會布局靜態字段,此時的_offset_of_static_fields字段的值為96。
我們需要分清相關類的表示方法,如下圖所示。

java.lang.Class對象是通過對應的Oop對象來保存類的靜態屬性,因此他們的實例大小不同,需要特殊的方式來計算他們的大小以及屬性遍歷。
Klass的屬性_java_mirror就指向保存該類靜態字段的Oop對象,可通過該屬性訪問類的靜態字段。 Oop是HotSpot的對象表示模型,在后面會詳細介紹。
(3)InstanceClassLoaderKlass類
沒有添加新的字段,增加了新的oop遍歷方法,主要用於類加載器依賴遍歷使用。
3、創建類的實例
創建InstanceKlass實例會調用InstanceKlass::allocate_instance_klass()方法。在創建時,會涉及到C++對new運算符的重載,通過重載new運算符來分配對象的內存空間,然后再調用類的構造函數初始化相應的屬性。方法的實現如下:
InstanceKlass* InstanceKlass::allocate_instance_klass(
ClassLoaderData* loader_data,
int vtable_len,
int itable_len,
int static_field_size,
int nonstatic_oop_map_size,
ReferenceType rt,
AccessFlags access_flags,
Symbol* name,
Klass* super_klass,
bool is_anonymous,
TRAPS
){
bool isinterf = access_flags.is_interface();
int size = InstanceKlass::size(
vtable_len,
itable_len,
nonstatic_oop_map_size,
isinterf,
is_anonymous
);
// Allocation
InstanceKlass* ik;
///////////////////////////////////////////////////////////////////////
if (rt == REF_NONE) {
if (name == vmSymbols::java_lang_Class()) { // 通過InstanceMirrorKlass對象表示java.lang.Class類
ik = new (loader_data, size, THREAD) InstanceMirrorKlass(
vtable_len,
itable_len,
static_field_size,
nonstatic_oop_map_size,
rt,
access_flags,
is_anonymous);
} else if (
name == vmSymbols::java_lang_ClassLoader() ||
(
SystemDictionary::ClassLoader_klass_loaded() &&
super_klass != NULL && // ClassLoader_klass為java_lang_ClassLoader
super_klass->is_subtype_of(SystemDictionary::ClassLoader_klass())
)
){ // 通過InstanceClassLoaderKlass對象表示java.lang.ClassLoader或相關子類
ik = new (loader_data, size, THREAD) InstanceClassLoaderKlass(
vtable_len,
itable_len,
static_field_size,
nonstatic_oop_map_size,
rt,
access_flags,
is_anonymous);
} else { // 通過InstanceKlass對象表示普通類
// normal class
ik = new (loader_data, size, THREAD) InstanceKlass(
vtable_len, itable_len,
static_field_size,
nonstatic_oop_map_size,
rt,
access_flags,
is_anonymous);
}
}
///////////////////////////////////////////////////////////////////////
else { // 通過InstanceRefKlass對象表示引用
// reference klass
ik = new (loader_data, size, THREAD) InstanceRefKlass(
vtable_len, itable_len,
static_field_size,
nonstatic_oop_map_size,
rt,
access_flags,
is_anonymous);
}
///////////////////////////////////////////////////////////////////////
// 添加所有類型到我們內部類加載器列表中,包括在根加載器中的類
// Add all classes to our internal class loader list here,
// including classes in the bootstrap (NULL) class loader.
// loader_data的類型為ClassLoaderData*,通過ClassLoaderData中的_klasses保持通過InstanceKlass._next_link屬性保持的列表
loader_data->add_class(ik);
Atomic::inc(&_total_instanceKlass_count);
return ik;
}
方法的實現比較簡單,當rt等於REF_NONE時,也就是為非Reference類型時,會根據類名創建對應C++類的對象。Class類創建InstanceMirrorKlass、ClassLoader類或ClassLoader的子類創建InstanceClassLoaderKlass類、普通類通過InstanceKlass來表示。當rt不為REF_NONE時,會創建InstanceRefKlass對象。
調用的size()函數在之前介紹InstanceKlass類時已經介紹過,這里不再介紹。得到size后會調用new重載運算符函數來開辟內存空間,如下:
void* Klass::operator new(size_t size, ClassLoaderData* loader_data, size_t word_size, TRAPS) throw() {
void* x = Metaspace::allocate( // 在元數據區分配內存空間
loader_data,
word_size,
false, /*read_only*/
MetaspaceObj::ClassType,
CHECK_NULL
);
return x;
}
可以看到,對於jdk1.8版本來說,Klass對象在元數據區分配內存。由於C++沒有像Java一樣的垃圾回收機制,所以Metaspace的內存需要自動管理和釋放,這一塊知識將在后面詳細介紹。
其它參考文章:
1、在Ubuntu 16.04上編譯OpenJDK8的源代碼(配視頻)
搭建過程中如果有問題可直接評論留言或加作者微信mazhimazh。
作者持續維護的個人博客 classloading.com。
B站上有HotSpot源碼分析相關視頻 https://space.bilibili.com/27533329
關注公眾號,有HotSpot源碼剖析系列文章!
