HotSpot原始碼分析之類模型
HotSpot採用了OOP-Klass模型描述Java的類和物件。Klass模型採用Klass類及相關子類的物件來描述具體的Java類。一般HotSpot JVM 在載入Java的Class 檔案時,會在方法區建立 Klass ,用來儲存Java類的元資料,包括常量池、欄位、方法等。
Klass模型中相關類的繼承體系如下圖所示。
Metadata是元資料類的基礎型別,除了Klass會直接繼承外,表示方法的Method與表示常量池的ConstantPool也會繼承,這裡只討論Klass繼承體系中涉及到的相關類。
整個Klass模型中涉及到的C++類主要提供了2個功能:
(1)提供C++層面的Java型別(包括Java類和Java陣列)表示,也就是用C++類的物件來描述Java型別。
(2)方法分派
這一篇文章重點介紹一下Klass這個基礎型別。
一個Klass物件(注意是Klass物件表示Java類的元資料,所以不同的Java類就用不同的Klass物件表示)代表一個Java類的元資料(相當於java.lang.Class
物件)。所以Klass中要有描述Java類中常量池、欄位、方法的能力,也就是能儲存這些資訊,同時還能提供一些方法供HotSpot JVM的開發者操作這些資訊。
Klass類及重要屬性的定義如下:
原始碼位置:hotspot/src/share/vm/oops/klass.hpp
class Klass : public Metadata { // ... protected: // note: put frequently-used fields together at start of klass structure // for better cache behavior (may not make much of a difference but sure won't hurt) enum { _primary_super_limit = 8 }; // The "layout helper" is a combined descriptor of object layout. // For klasses which are neither instance nor array, the value is zero. // // For instances, layout helper is a positive number, the instance size. // This size is already passed through align_object_size and scaled to bytes. // The low order bit is set if instances of this class cannot be // allocated using the fastpath. // // For arrays, layout helper is a negative number, containing four // distinct bytes, as follows: // MSB:[tag, hsz, ebt, log2(esz)]:LSB // where: // tag is 0x80 if the elements are oops, 0xC0 if non-oops // hsz is array header size in bytes (i.e., offset of first element) // ebt is the BasicType of the elements // esz is the element size in bytes // This packed word is arranged so as to be quickly unpacked by the // various fast paths that use the various subfields. // // The esz bits can be used directly by a SLL instruction, without masking. // // Note that the array-kind tag looks like 0x00 for instance klasses, // since their length in bytes is always less than 24Mb. // // Final note: This comes first, immediately after C++ vtable, // because it is frequently queried. jint _layout_helper; // The fields _super_check_offset, _secondary_super_cache, _secondary_supers // and _primary_supers all help make fast subtype checks. See big discussion // in doc/server_compiler/checktype.txt // // Where to look to observe a supertype (it is &_secondary_super_cache for // secondary supers, else is &_primary_supers[depth()]. juint _super_check_offset; // Class name. Instance classes: java/lang/String, etc. Array classes: [I, // [Ljava/lang/String;, etc. Set to zero for all other kinds of classes. Symbol* _name; // Cache of last observed secondary supertype Klass* _secondary_super_cache; // Array of all secondary supertypes Array<Klass*>* _secondary_supers; // Ordered list of all primary supertypes Klass* _primary_supers[_primary_super_limit]; // java/lang/Class instance mirroring this class oop _java_mirror; // Superclass Klass* _super; // First subclass (NULL if none); _subklass->next_sibling() is next one Klass* _subklass; // Sibling link (or NULL); links all subklasses of a klass Klass* _next_sibling; // All klasses loaded by a class loader are chained through these links Klass* _next_link; // The VM's representation of the ClassLoader used to load this class. // Provide access the corresponding instance java.lang.ClassLoader. ClassLoaderData* _class_loader_data; AccessFlags _access_flags; // Access flags. The class/interface distinction is stored here. markOop _prototype_header; // Used when biased locking is both enabled and disabled for this type ... }
下表對各個屬性進行了簡單的介紹。
欄位名 | 作用 |
_layout_helper |
物件佈局的綜合描述符。如果不是InstanceKlass或ArrayKlass,值為0。如果是InstantceKlass或 ArrayKlass時,這個值是個組合數字。 (1)對InstanceKlass而言,組合數字中包含有表示物件的、以位元組為單位的記憶體佔用大小,也就是說InstanceKlass物件表示Java類,由這個Java類建立的物件所需要的大小。 (2)對ArrayKlass而言,該值是一個組合數字,包含4部分,具體怎麼組合和解析由子類實現:
|
_name | 類名,如java/lang/String,[Ljava/lang/String |
_primary_supers |
_primary_supers代表了這個類的父類,其型別是個Klass指標陣列,大小固定為8。例如IOException是Exception的子類, 而Exception又是Throwable的子類。所以表示IOException類的_primary_supers屬性值為: [Throwable, Exception, IOException]。如果繼承鏈過長,也就是當前類加上繼承的類多於8個(預設值,可通過命令更改)時, 會將多出來的類儲存到secondary_supers陣列中 |
_super_check_offset |
快速查詢supertype的一個偏移量,這個偏移量是相對於Klass物件起始地址的偏移量。如果當前類是IOException, 那麼這個屬性就指向_primary_supers陣列中儲存IOException的位置。當儲存的類多於8個時,值與secondary_super_cache 相等 |
_secondary_supers |
Klass指標陣列,一般儲存Java類實現的介面,偶爾還會儲存Java類的父類 |
_secondary_super_cache |
Klass指標,儲存上一次查詢父類的結果 |
_java_mirror | oopDesc型別的指標,儲存的是當前Klass物件表示的Java類所對應的java.lang.Class物件,可以據此訪問類的靜態屬性 |
_super | Klass指標,指向Java類的直接父類 |
_subklass | Klass指標,指向Java類的直接子類,由於直接子類可能有多個,所以通過_next_sibling連線起來 |
_next_sibling | Klass指標,該類的下一個子類,也就是通過_subklass->next_sibling()獲取_subklass的兄弟子類 |
_next_link | Klass指標,ClassLoader載入的下一個Klass |
_class_loader_data | ClassLoaderData指標,可以通過此屬性找到載入該Java類的ClassLoader |
_access_flags | 獲取Java類的修飾符,如private、final、static、abstract 、native等 |
_prototype_header | 在鎖的實現過程中非常重要,後續在介紹鎖時會介紹 |
可以看到,能夠通過Klass類中的相關屬性儲存Java類定義的一些資訊,如_name儲存Java類的名稱、_super儲存Java類實現的型別等。Klass類是Klass模型中定義的C++類的基類,所以此類物件只儲存了Java類的一些必要資訊,其它如常量池、方法、欄位等會通過Klass類的具體子類的相關屬性來儲存。
類的屬性比較多,我們在後面解釋類的過程中可以看到對相關屬性的賦值操作。
1、_layout_helper
_layout_helper是一個組合屬性。如果當前的類表示一個Java陣列型別時,這個屬性的值比較複雜。通常會呼叫如下函式生成值:
jint Klass::array_layout_helper(BasicType etype) { assert(etype >= T_BOOLEAN && etype <= T_OBJECT, "valid etype"); bool isobj = (etype == T_OBJECT); int tag = isobj ? _lh_array_tag_obj_value : _lh_array_tag_type_value; // Note that T_ARRAY is not allowed here. // 在64位系統下,存放_metadata的空間大小是8位元組,_mark是8位元組, // length是4位元組,物件頭為20位元組,由於要按8位元組對齊,所以會填充4位元組,最終佔用24位元組 int hsize = arrayOopDesc::base_offset_in_bytes(etype); // hsize表示陣列頭部大小 int esize = type2aelembytes(etype); // Java基本型別元素需要佔用的位元組數 int esz = exact_log2(esize); // 例如Java基本型別元素佔用4個位元組,則儲存的是2 int lh = array_layout_helper(tag, hsize, etype, esz); return lh; }
其中用到2個列舉常量,如下:
_lh_array_tag_type_value = ~0x00, _lh_array_tag_obj_value = ~0x01
_lh_array_tag_type_value的二進位制表示為32個1:11111111111111111111111111111111,其實也就是0xC0000000 >> 30,做算術右移,負數的最高位補1。
_lh_array_tag_obj_value的二進位制表示為最高位31個1:11111111111111111111111111111110,其實也就是0x80000000 >> 30,做自述右移,負數的最高位補1。
呼叫的arrayOopDesc::base_offset_in_bytes()函式及呼叫的相關函式的實現如下:
// Returns the offset of the first element. static int base_offset_in_bytes(BasicType type) { return header_size(type) * HeapWordSize; } // Should only be called with constants as argument (will not constant fold otherwise) // Returns the header size in words aligned to the requirements of the array object type. static int header_size(BasicType type) { size_t typesize_in_bytes = header_size_in_bytes(); if( Universe::element_type_should_be_aligned(type) ){ return (int)align_object_offset( typesize_in_bytes/HeapWordSize ); }else { return (int)typesize_in_bytes/HeapWordSize; } } // 在64位系統下,存放_metadata的空間大小是8位元組,_mark是8位元組,length是4位元組,物件頭為20位元組, // 由於要按8位元組對齊,所以會填充4位元組,最終佔用24位元組 static int header_size_in_bytes() { intptr_t temp = length_offset_in_bytes() + sizeof(int); // sizeof(int) size_t hs = align_size_up(temp,HeapWordSize); return (int)hs; } // The _length field is not declared in C++. It is allocated after the // declared nonstatic fields in arrayOopDesc if not compressed, otherwise // it occupies the second half 下半場 of the _klass field in oopDesc. static int length_offset_in_bytes() { if(UseCompressedClassPointers){ return klass_gap_offset_in_bytes(); }else{ sizeof(arrayOopDesc); } }
程式碼的邏輯非常清晰,這裡不過多介紹。最終會在Klass::array_layout_helper()函式中呼叫array_layout_helper()函式完成屬性值的計算。這個函式的實現如下:
static jint array_layout_helper(jint tag, int hsize, BasicType etype, int log2_esize) { return (tag << _lh_array_tag_shift) // 左移30位 | (hsize << _lh_header_size_shift) // 左移16位 | ((int)etype << _lh_element_type_shift) // 左移1位 | (log2_esize << _lh_log2_element_size_shift); // 左移0位 }
對_lh_array_tag_type_value與_lh_array_tag_obj_value值左移30位後,第32位上肯定為1,所以最終計算出的值是一個小於0的數。而非陣列型別,一般由InstanceKlass物件表示的Java類來說,計算的屬性值如下:
static jint instance_layout_helper(jint size, bool slow_path_flag) { if(slow_path_flag){ return (size << LogHeapWordSize) | _lh_instance_slow_path_bit; }else{ return (size << LogHeapWordSize) | 0; // LogHeapWordSize=3 } }
size為物件的、以位元組為單位的記憶體佔用大小,所以肯定是一個正數。這樣就可以通過_layout_helper來判斷型別了。
2、_primary_supers、_super_check_offset、_secondary_supers與_secondary_super_cache
這幾個屬性完全是為了加快判定父子關係等邏輯而加入的。下面看initialize_supers()函式中是如何初始化這幾個屬性的。函式的第1部分實現如下:
原始碼位置:hotspot/src/share/vm/oops/klass.cpp void Klass::initialize_supers(Klass* k, TRAPS) { // // 當前類的父類k可能為NULL,例如Object的父類為NULL if (k == NULL) { set_super(NULL); _primary_supers[0] = this; } // k就是當前類的直接父類,如果有父類,那麼super()一般為NULL,如果k為NULL,那麼就是Object類,從下面的斷言也可以看出 else if (k != super() || k == SystemDictionary::Object_klass()) { set_super(k); // 設定Klass的_super屬性 Klass* sup = k; int sup_depth = sup->super_depth(); juint my_depth = MIN2(sup_depth + 1, (int)primary_super_limit()); // primary_super_limit()方法得到的值一般預設為8 // 當父類的的繼承鏈長度大於等於primary_super_limit()時,當前的深度只能是primary_super_limit(),也就是8,因為_primary_supers中只儲存8個類 if (!can_be_primary_super_slow()){ my_depth = primary_super_limit(); // 8 } for (juint i = 0; i < my_depth; i++) { // my_depth預設的值為8 _primary_supers[i] = sup->_primary_supers[i]; } Klass* *super_check_cell; if (my_depth < primary_super_limit()) { // primary_super_limit()的預設為8 _primary_supers[my_depth] = this; super_check_cell = &_primary_supers[my_depth]; } else { // Overflow of the primary_supers array forces me to be secondary. super_check_cell = &_secondary_super_cache; } // 通過_super_check_offset這個偏移量可以快速定義到當前在_primary_supers中的位置 juint _super_check_offset = (address)super_check_cell - (address) this; set_super_check_offset( _super_check_offset ); // 設定Klass中的_super_check_offset屬性 } // 第2部分程式碼在下面 }
在設定當前類的父類時通常都會呼叫initialize_supers方法,同時也會設定_primary_supers、super_check_offset,如果繼承鏈過長,還有可能設定secondary_supers、secondary_super_cache等值。這此屬性中儲存繼承鏈中涉及到的類以方便快速的進行類關係之間的判斷,例如父子關係的判斷。
方法的第2部分程式碼實現如下:
if (secondary_supers() == NULL) { KlassHandle this_kh (THREAD, this); // Now compute the list of secondary supertypes. // Secondaries can occasionally be on the super chain, // if the inline "_primary_supers" array overflows. int extras = 0; Klass* p; for (p = super(); // 當p不為NULL並且p已經儲存在了_secondary_supers陣列中時,條件為true // 也就是當前類的父類多於8個,將多出來的儲存到了_secondary_supers陣列中了 !(p == NULL || p->can_be_primary_super()); p = p->super()) { ++extras; } // 計算secondaries需要的大小,因為secondaries陣列中還需要儲存當前類的所有實現介面(包括直接和間接實現的介面) // Compute the "real" non-extra secondaries. GrowableArray<Klass*>* secondaries = compute_secondary_supers(extras); if (secondaries == NULL) { // extras為0時直接返回,不需要額外的處理 // secondary_supers set by compute_secondary_supers return; } GrowableArray<Klass*>* primaries = new GrowableArray<Klass*>(extras); for ( p = this_kh->super(); !(p == NULL || p->can_be_primary_super()); p = p->super() ){ primaries->push(p); } // Combine the two arrays into a metadata object to pack the array. // The primaries are added in the reverse order, then the secondaries. int new_length = primaries->length() + secondaries->length(); Array<Klass*>* s2 = MetadataFactory::new_array<Klass*>(class_loader_data(), new_length, CHECK); int fill_p = primaries->length(); for (int j = 0; j < fill_p; j++) { s2->at_put(j, primaries->pop()); // add primaries in reverse order.也就是父類永遠在陣列前,子類永遠在陣列後 } for( int j = 0; j < secondaries->length(); j++ ) { s2->at_put(j+fill_p, secondaries->at(j)); // add secondaries on the end. } this_kh->set_secondary_supers(s2); // 設定_secondary_supers屬性 }
可以看到,會將父親繼承鏈中多於8個的父類儲存到secondary_supers陣列中,不過因為繼承鏈一般都不會多於8個,所以設定了預設值為8。
下面舉個例子,看看這幾個屬性是如何儲存值的,如下:
interface IA{} interface IB{} class A{} class B extends A{} class C extends B{} class D extends C{} public class Test extends D implements IA,IB {}
配置-XX:FastSuperclassLimit=3後,_primary_supers陣列中就最多隻能儲存3個類了。值如下:
_primary_supers[Object,A,B] _secondary_supers[C,D,Test,IA,IB]
由於當前類Test的繼承鏈過長,導致C、D和Test只能儲存到_secondary_supers。所以此時_super_check_offset會指向C,也就是_secondary_supers中儲存的第1個元素。
下面舉個例子,看一下這幾個屬性如何應用。例如is_subtype_of()方法,實現如下:
// subtype check: true if is_subclass_of, or if k is interface and receiver implements it bool is_subtype_of(Klass* k) const { // 判斷當前類是否為k的子類 juint off = k->super_check_offset(); Klass* sup = *(Klass**)( (address)this + off ); const juint secondary_offset = in_bytes(secondary_super_cache_offset()); if (sup == k) { return true; } else if (off != secondary_offset) { return false; } else { return search_secondary_supers(k); } }
當通過_super_check_offset獲取到的類與k相同時,那麼k存在於當前類的繼承鏈上,肯定有父子關係。
如果k存在於_primary_supers陣列中,那麼通過_super_check_offset就可快速判斷,如果k存在於_secondary_supers中,那麼需要呼叫search_secondary_supers()來判斷。
呼叫的search_secondary_supers()方法的實現如下:
bool Klass::search_secondary_supers(Klass* k) const { // Put some extra logic here out-of-line, before the search proper. // This cuts down the size of the inline method. // This is necessary, since I am never in my own secondary_super list. if (this == k){ return true; } // Scan the array-of-objects for a match int cnt = secondary_supers()->length(); for (int i = 0; i < cnt; i++) { if (secondary_supers()->at(i) == k) { ((Klass*)this)->set_secondary_super_cache(k); // 設定_secondary_super_cache屬性,儲存這次查詢的結果 return true; } } return false; }
可以看到,屬性_secondary_super_cache儲存了這一次父類查詢的結果。查詢的邏輯很簡單,遍歷_secondary_supers陣列中的值並比較即可。
3、_super、_subklass、_next_sibling
由於Java類是單繼承,所以可通過_super、_subklass、_next_sibling屬性可直接找到當前類的父類或所有子類。呼叫Klass::append_to_sibling_list()函式設定_next_sibling與_subklass的值,方法的實現如下:
void Klass::append_to_sibling_list() { // add ourselves to superklass' subklass list InstanceKlass* super = superklass(); // 獲取到_super屬性的值 if (super == NULL) return; // special case: class Object Klass* prev_first_subklass = super->subklass_oop(); // 獲取_subklass屬性的值 if (prev_first_subklass != NULL) { // set our sibling to be the superklass' previous first subklass set_next_sibling(prev_first_subklass); // 設定_next_sibling屬性的值 } // make ourselves the superklass' first subklass super->set_subklass(this); // 設定_subklass屬性的值 }
方法的實現邏輯很簡單,這裡不過多介紹。
其它文章:
1、在Ubuntu 16.04上編譯OpenJDK8的原始碼(配視訊)
2、除錯HotSpot原始碼(配視訊)
3、HotSpot專案結構
4、HotSpot的啟動過程(配視訊進行原始碼分析)
5、HotSpot原始碼分析之C++物件的記憶體佈局
搭建過程中如果有問題可直接評論留言或加作者微信mazhimazh。
作者持續維護的個人部落格 classloading.com。
B站上有HotSpot原始碼分析相關視訊 https://space.bilibili.com/27533329
關注公眾號,有HotSpot原始碼剖析系列文章!
&n