官术网_书友最值得收藏!

3.3 類

Klass是一個(gè)抽象基類,它定義了一些接口(純虛函數(shù)),由InstanceKlass繼承并實(shí)現(xiàn)這些接口,兩者結(jié)合可以描述一個(gè)Java類的方法有哪些、字段有哪些、父類是否存在等。Klass提供了相當(dāng)多的關(guān)于類的信息,同樣可以使用HotSpot Debugger可視化,如圖3-5所示。

圖3-5 使用jhsdb hsdb命令可視化查看klass

InstanceKlass在虛擬機(jī)層描述大部分的Java類,但有少部分Java類有特殊語意:普通類的對(duì)象在垃圾回收過程中只需要遍歷所有實(shí)例字段;java.lang.Class的對(duì)象需要遍歷實(shí)例字段和靜態(tài)字段;java.lang.ref.*的對(duì)象需要處理被引用對(duì)象;java.lang.ClassLoader需要處理類加載數(shù)據(jù)。這些類的特殊行為不能用InstanceKlass統(tǒng)一表示,因此InstanceKlass之下派生出InstanceMirrorKlass描述java.lang.Class類,InstanceRefKlass描述java.lang.ref.*類,InstanceClassLoaderKlass描述java.lang.ClassLoader類。

3.3.1 字段遍歷

在垃圾回收過程中常見的任務(wù)是遍歷一個(gè)對(duì)象的所有字段。以G1為例,在Full GC過程中會(huì)標(biāo)記所有成員對(duì)象,如代碼清單3-5所示:

代碼清單3-5 字段遍歷


inline void G1FullGCMarker::follow_object(oop obj) {
    if (obj->is_objArray()) {          // 如果對(duì)象是數(shù)組,則標(biāo)記每個(gè)數(shù)組成員
        follow_array((objArrayOop)obj);
    } else {                           // 否則標(biāo)記對(duì)象的每個(gè)非靜態(tài)數(shù)據(jù)成員
        obj->oop_iterate(mark_closure());    
    }
}
// 該方法由上面的obj->oop_iterate調(diào)用
ALWAYSINLINE void InstanceKlass::oop_oop_iterate_oop_maps(...) {
    OopMapBlock* map             = start_of_nonstatic_oop_maps();
    OopMapBlock* const end_map = map + nonstatic_oop_map_count();
    for (; map < end_map; ++map) {     // 遍歷每個(gè)OopMapBlock
        oop_oop_iterate_oop_map<T>(map, obj, closure); 
    }
}

調(diào)用obj->oop_iterate后經(jīng)過一個(gè)較長(zhǎng)的調(diào)用鏈,會(huì)執(zhí)行oop_oop_iterate_oop_maps,根據(jù)代碼不難看出它的行為是獲取開始OopMapBlock和結(jié)束OopMapBlock,然后遍歷這些OopMapBlock。OopMapBlock存儲(chǔ)了該對(duì)象的字段偏移和個(gè)數(shù),分別用offset和count表示。offset表示第一個(gè)字段相對(duì)于對(duì)象頭的偏移,count表示對(duì)象有多少個(gè)字段。另外,如果有父類,則再用一個(gè)OopMapBlock表示父類,因此通過遍歷對(duì)象的所有OopMapBlock就能訪問對(duì)象的全部字段。

3.3.2 虛表

如果使用C++編程,會(huì)用一個(gè)Node表示基類,由AddNode繼承Node,它們都有一個(gè)print方法。現(xiàn)在有一個(gè)變量Node *n=new AddNode,靜態(tài)類型為Node,動(dòng)態(tài)類型為AddNode,調(diào)用n->print()函數(shù)會(huì)根據(jù)n的動(dòng)態(tài)類型進(jìn)行函數(shù)派發(fā),由于n的動(dòng)態(tài)類型為AddNode所以調(diào)用AddNode::print。在這個(gè)過程中,需要為每個(gè)對(duì)象插入一個(gè)虛表。虛表是一個(gè)由函數(shù)指針構(gòu)成的數(shù)組,可以添加編譯參數(shù)輸出它[1]。以上面的變量為例,Node虛表的第一個(gè)元素是指向Node::print的函數(shù)指針,AddNode虛表的第一個(gè)元素是指向AddNode::print的指針,n在運(yùn)行時(shí)可以通過查找虛表來定位正確的方法(AddNode::print)。

如果使用Java編程,情況就不一樣了。根據(jù)前面的描述,每個(gè)Java對(duì)象即oop都有對(duì)象頭,對(duì)象頭里面有一個(gè)_klass指向?qū)ο蟮恼_的InstanceKlass類型,而InstanceKlass包含了類的所有方法以及父類信息,當(dāng)執(zhí)行n.print()時(shí),JVM可以(但是并沒有)從對(duì)象n的對(duì)象頭里取出_klass,找到描述AddNode類的InstanceKlass,再在其中尋找print方法。這一過程并不需要虛表參與。正如上面討論的,虛表是Java動(dòng)態(tài)派發(fā)的優(yōu)化而不是必要組件,就像native入口之于Method,Java的虛表也是位于InstanceKlass之外,如圖3-6所示。

圖3-6 InstanceKlass布局

第2章提到類會(huì)經(jīng)歷加載、鏈接、初始化三個(gè)階段,這里我們只討論了鏈接階段的一些步驟,實(shí)際上它還會(huì)執(zhí)行很多額外的步驟,如虛表的初始化也是在鏈接階段進(jìn)行的。HotSpot會(huì)在類加載階段計(jì)算出虛表大小,然后在鏈接階段使用klassVtable::initialize_vtable()初始化虛表,如代碼清單3-6所示:

代碼清單3-6 虛表初始化


void klassVtable::initialize_vtable(bool checkconstraints, TRAPS) {
    ...
    // 處理當(dāng)前類的所有方法
    for(int i = 0; i < len; i++) {
        methodHandle mh(THREAD, methods->at(i));
        // 該方法是否為虛方法
        bool needs_new_entry = update_inherited_vtable(...);
        // 如果是,則需要更新當(dāng)前類的虛表索引
        if (needs_new_entry) {
        put_method_at(mh(), initialized);
        mh()->set_vtable_index(initialized); 
        initialized++;
        }
    }
    ...// 和前面類似,處理default方法
}

update_inherited_vtable會(huì)經(jīng)過一系列檢查來確定一個(gè)方法是否為虛方法以及是否需要加入類的虛表。上述例子的Node與AddNode經(jīng)過虛表初始化后的vtable如圖3-7所示。

圖3-7 Node與AddNode經(jīng)過虛表初始化后的vtable

也可以開啟VM參數(shù)-Xlog:vtables=trace查看所有類的虛表的創(chuàng)建過程。在調(diào)用虛方法時(shí)虛擬機(jī)會(huì)在運(yùn)行時(shí)常量池中查找n的靜態(tài)類型Node的print方法,獲取它在Node虛表中的index,接著用index定位動(dòng)態(tài)類型AddNode虛表中的虛方法進(jìn)行調(diào)用。第一步的運(yùn)行時(shí)常量池在HotSpot VM中的表示是oops/ConstantPoolCache,它也是對(duì)象和類模型的一部分。

[1] gcc使用-fdump-class-hierarchy輸出虛表,clang使用-Xclang -fdump-vtable-layouts輸出虛表,msvc使用/d1reportAllClassLayout輸出虛表。

主站蜘蛛池模板: 镶黄旗| 开阳县| 泰来县| 固镇县| 叶城县| 五台县| 莒南县| 田东县| 临沧市| 金平| 华亭县| 本溪| 凌源市| 塘沽区| 乌拉特前旗| 平南县| 措美县| 南阳市| 苏尼特右旗| 斗六市| 布尔津县| 鹤庆县| 沾化县| 兴和县| 绥德县| 开化县| 城固县| 江都市| 军事| 永福县| 天台县| 开江县| 新余市| 天峨县| 南投市| 赤城县| 巩义市| 南华县| 山西省| 霍邱县| 富阳市|