手把手教你擼一個(gè)Mini JVM系列(5)之源碼分析 -- 常量池、訪問標(biāo)志照藻、類索引

引子: 對(duì)于之前分析的Mini JVM的實(shí)現(xiàn)原理, 這里再加幾篇關(guān)于源碼的分析, 目的是為了可以更形象的展現(xiàn)之前所說的內(nèi)容, 同時(shí)在分析源碼的同時(shí)如果有同學(xué)有更好的實(shí)現(xiàn)的方法也可以提出來一起交流.

1. 解析常量池

在之前的文章中已經(jīng)提到過, 常量池是整個(gè)class文件中十分重要的一部分, 其存在的意思主要是可以減少class中的重復(fù)數(shù)據(jù), 使class文件可以更加的小. 同時(shí)執(zhí)行引擎在執(zhí)行的時(shí)候也會(huì)引用到常量池中的內(nèi)容. 所以常量池的解析對(duì)于整個(gè)JVM來說是很關(guān)鍵的一步.

首先再來看一下常量池在class存在的形式

class-file

<div style="margin-left:200px">圖1-1</div>

嗯, 沒錯(cuò), 都是一些二進(jìn)制數(shù)值, 所以解析常量池的工作其實(shí)就是將這個(gè)數(shù)值根據(jù)規(guī)則轉(zhuǎn)成java中的一個(gè)一個(gè)的類.

1.1 常量池類結(jié)構(gòu)圖

之前常量池的文章中介紹過, 常量池就是由一個(gè)個(gè)的常量項(xiàng)組成的, 因此可以得出以下的類結(jié)構(gòu)圖

constant-pool-uml

<div style="margin-left:200px">圖1-1 constant-pool-uml (點(diǎn)擊看大圖)</div>

這個(gè)結(jié)構(gòu)應(yīng)該很容易就可以想到, 下面來看一下代碼是如何實(shí)現(xiàn)的.

1.2 代碼實(shí)現(xiàn)

解析常量池:
傳入的參數(shù)就是class文件的字節(jié)碼數(shù)組

    private int _parseConstantPool(byte[] contents) {
           // 由class文件的結(jié)構(gòu)規(guī)范可知, 常量吃的長度在class文件的第8個(gè)字節(jié)到第10(不包括)個(gè)字節(jié)記節(jié)
           // 所以這里的CONSTANT_POOL_LENGTH_START和CONSTANT_POOL_LENGTH_END分別是8和10
        Integer constantPoolLength = byte2Int(Arrays.copyOfRange(contents, CONSTANT_POOL_LENGTH_START, CONSTANT_POOL_LENGTH_END));
        // 常量池長度數(shù)據(jù)之后緊接著就是常量池的具體內(nèi)容, 所以pos的值就是10
        int pos = CONSTANT_POOL_START;
        List<AbstractConstant> abstractConstant = new ArrayList<>();
        ConstantPool pool = new ConstantPool(abstractConstant);
        // 因?yàn)槌A砍刂谐A宽?xiàng)的序號(hào)是從1開始的, 所以遍歷的時(shí)候需要是常量項(xiàng)長度-1
        // 同時(shí)保留的第0項(xiàng)用于表示不引用任何的常量項(xiàng), 因此這里一開始就創(chuàng)建一個(gè)NullConstant
        abstractConstant.add(new NullConstant());
        for (int i = 0; i < constantPoolLength - 1; i++) {
            byte tag = contents[pos];
            pos = pos + 1;
            // 根據(jù)tag的值來判斷接下來的常量項(xiàng)是到底是什么類型
            // 不同的常量項(xiàng)的具體數(shù)據(jù)內(nèi)容不同, 需要進(jìn)行單獨(dú)的解析
            // 解析完每一個(gè)常量項(xiàng)都需要更新pos的值, 其一直指向下一個(gè)常量項(xiàng)的tag位置
            // 每一個(gè)常量項(xiàng)的數(shù)據(jù)格式定義可以參考o(jì)racle jvm規(guī)范
            switch (tag) {
                case CONSTANT_UTF8: {
                    int length = byte2Int(Arrays.copyOfRange(contents, pos, pos + 2));
                    byte[] content = Arrays.copyOfRange(contents, pos + 2, pos + 2 + length);
                    UTF8Constant utf8Constant = new UTF8Constant(pool, tag, length, content);
                    abstractConstant.add(utf8Constant);
                    pos += (2 + length);
                    break;
                }
                case CONSTANT_INTEGER: {
                    // TODO: 17/6/6 后序添加
                    break;
                }
                case CONSTANT_FLOAT: {
                    // TODO: 17/6/6 后序添加
                    break;
                }
                case CONSTANT_LONG: {
                    // TODO: 17/6/6 后序添加
                    break;
                }
                case CONSTANT_DOUBLE: {
                    // TODO: 17/6/6 后序添加
                    break;
                }
                case CONSTANT_CLASS: {
                    int nameIndex = byte2Int(Arrays.copyOfRange(contents, pos, pos + 2));
                    ClassConstant classConstant = new ClassConstant(pool, tag, nameIndex);
                    abstractConstant.add(classConstant);
                    pos += 2;
                    break;
                }
                case CONSTANT_STRING: {
                    Integer stringIndex = byte2Int(Arrays.copyOfRange(contents, pos, pos + 2));
                    StringConstant stringConstant = new StringConstant(pool, tag, stringIndex);
                    abstractConstant.add(stringConstant);
                    pos += 2;
                    break;
                }
                case CONSTANT_FIELD_REF: {
                    Integer classIndex = byte2Int(Arrays.copyOfRange(contents, pos, pos + 2));
                    Integer nameAndTypeIndex = byte2Int(Arrays.copyOfRange(contents, pos + 2, pos + 4));
                    FieldRefConstant fieldRefConstant = new FieldRefConstant(pool, tag, classIndex, nameAndTypeIndex);
                    abstractConstant.add(fieldRefConstant);
                    pos += 4;
                    break;
                }
                case CONSTANT_METHOD_REF: {
                    Integer classIndex = byte2Int(Arrays.copyOfRange(contents, pos, pos + 2));
                    Integer nameAndTypeIndex = byte2Int(Arrays.copyOfRange(contents, pos + 2, pos + 4));
                    MethodRefConstant methodRefConstant = new MethodRefConstant(pool, tag, classIndex, nameAndTypeIndex);
                    abstractConstant.add(methodRefConstant);
                    pos += 4;
                    break;
                }
                case CONSTANT_INTERFACE_METHOD_REF: {
                    // TODO: 17/6/6 后序添加
                    break;
                }
                case CONSTANT_NAME_AND_TYPE: {
                    Integer nameIndex = byte2Int(Arrays.copyOfRange(contents, pos, pos + 2));
                    Integer descriptorIndex = byte2Int(Arrays.copyOfRange(contents, pos + 2, pos + 4));
                    NameAndTypeConstant nameAndTypeConstant = new NameAndTypeConstant(pool, tag, nameIndex, descriptorIndex);
                    abstractConstant.add(nameAndTypeConstant);
                    pos += 4;
                    break;
                }
                case CONSTANT_METHOD_HANDLE: {
                    // TODO: 17/6/6 后序添加
                    break;
                }
                case CONSTANT_METHOD_TYPE: {
                    // TODO: 17/6/6 后序添加
                    break;
                }
                case CONSTANT_INVOKE_DYNAMIC: {
                    // TODO: 17/6/6 后序添加
                    break;
                }
                default:
                    throw new RuntimeException("class文件常量池結(jié)構(gòu)不正確");
            }
        }
        classFile.setConstantPool(pool);
        return pos;
    }

2. 解析訪問標(biāo)志

由之前的class文件的結(jié)構(gòu)可知, 常量池之后緊跟著的就是類的訪問標(biāo)志, 也就是那些private, abstract之類的東西. class文件中使用2個(gè)字節(jié)來表示這些內(nèi)容, 具體的說是用16bit來表示這些內(nèi)容, 因?yàn)檫@兩個(gè)字節(jié)的每一位都是有意義的, 當(dāng)某一位的值為1時(shí)就表示有某個(gè)修飾符. 所以解析訪問標(biāo)志實(shí)際上就是判斷每一位是否是1.

代碼如下:

class修飾符的枚舉定義:

/**
 * @author tonyhui
 * @since 17/6/5
 */
public enum ClassAccessFlag {
     // 每一個(gè)對(duì)于類合法的修飾符都會(huì)在這里進(jìn)行定義, code代表的就是該修飾符具體所在的bit
    ACC_PUBLIC(0X0001, "PUBLIC"),
    ACC_FINAL(0x0010, "FINAL"),
    ACC_SUPER(0x0020, "SUPER"),
    ACC_ABSTRACT(0x0400, "ABSTRACT"),
    ACC_SYNTHETIC(0x1000, "SYNTHETIC"),
    ACC_ANNOTATION(0x2000, "ANNOTATION"),
    ACC_ENUM(0x4000, "ENUM");

    private int code;
    private String name;

    ClassAccessFlag(int code, String name) {
        this.code = code;
        this.name = name;
    }

    public int getCode() {
        return code;
    }

    public String getName() {
        return name;
    }
}

解析class的修飾符

     /**
     * 解析Class的修飾符
     */
    private int _parseClassAccessFlag(byte[] contents, int accessFlagStart) {
        int accessFlag = byte2Int(Arrays.copyOfRange(contents, accessFlagStart, accessFlagStart + 2));
        // 由上面的分析可以知道, 只要將正在解析的類的修飾符的值與上面定義的枚舉進(jìn)行位運(yùn)算就可以知道該類有哪些修飾符
        // 寫到這里突然發(fā)現(xiàn)這個(gè)過程可以不用一個(gè)一個(gè)if進(jìn)行判斷而是可以通過一個(gè)循環(huán)進(jìn)行實(shí)現(xiàn), 所以寫文章的好處之一就是有的時(shí)候會(huì)靈光一現(xiàn), 想到其他的更好的實(shí)現(xiàn)方法
        List<ClassAccessFlag> classAccessFlags = new ArrayList<>();
        if ((accessFlag & ACC_PUBLIC.getCode()) != 0) {
            classAccessFlags.add(ACC_PUBLIC);
        }
        if ((accessFlag & ACC_FINAL.getCode()) != 0) {
            classAccessFlags.add(ACC_FINAL);
        }
        if ((accessFlag & ACC_SUPER.getCode()) != 0) {
            classAccessFlags.add(ACC_SUPER);
        }
        if ((accessFlag & ACC_ABSTRACT.getCode()) != 0) {
            classAccessFlags.add(ACC_ABSTRACT);
        }
        if ((accessFlag & ACC_SYNTHETIC.getCode()) != 0) {
            classAccessFlags.add(ACC_SYNTHETIC);
        }
        if ((accessFlag & ACC_ENUM.getCode()) != 0) {
            classAccessFlags.add(ACC_ENUM);
        }
        classFile.setAccessFlag(classAccessFlags);
        return accessFlagStart + 2;
    }

3. 解析類的索引

解析類的索引可以說是很簡單的了, 其實(shí)就是找到該類和其父類在常量池中的索引項(xiàng), 也就是找到該類和其父類在常量池中的索引. 這些內(nèi)容在常量項(xiàng)中本身就存在, 這里僅僅是引用一下, 但是jvm規(guī)范單獨(dú)將這部分?jǐn)?shù)據(jù)提取出來表示我想是為了之后獲取類的信息可以更加的方便, 而不用再到常量池中一個(gè)個(gè)的找. 畢竟類的信息對(duì)于解析一個(gè)類是時(shí)常要用到的.

解析類的索引

    /**
     * 解析Class和其父類在常量池中的索引
     */
    private int _parseClassIndex(byte[] contents, int classIndexStart) {
        int thisClassIndex = byte2Int(Arrays.copyOfRange(contents, classIndexStart, classIndexStart + 2));
        int superClassIndex = byte2Int(Arrays.copyOfRange(contents, classIndexStart + 2, classIndexStart + 4));
        ClassIndex classIndex = new ClassIndex(thisClassIndex, superClassIndex);
        classFile.setClassIndex(classIndex);
        return classIndexStart + 4;
    }

這段代碼沒什么好解釋的, 可以說是簡單的到不能再簡單了, thisClassIndex和superClassIndex的值一定是常量池的某個(gè)常量項(xiàng)的索引值(如果這個(gè)class是合法的).

其實(shí)在解析完class index后緊跟的是interface index, 也就是這個(gè)類實(shí)現(xiàn)的接口的索引, 但是我的這個(gè)mini jvm要解析的類沒有實(shí)現(xiàn)接口, 所以這個(gè)就不剖析了, 但是其實(shí)現(xiàn)的方式和解析class index實(shí)際上是一樣的.

4. 總結(jié)

整個(gè)mini jvm的代碼實(shí)現(xiàn)還不是很完整, 即使是已經(jīng)實(shí)現(xiàn)的功能也還有很多可以優(yōu)化的地方. 之后的計(jì)劃是一遍完善功能一遍繼續(xù)解析mini jvm的代碼, 后面解析方法和字段的代碼也是很關(guān)鍵的, 還有最后的執(zhí)行引擎的實(shí)現(xiàn)是整個(gè)mini jvm的核心. 最后如果有必要再對(duì)一些Miscellaneous的實(shí)現(xiàn)進(jìn)行解析一下.

5. 本系列其他文章

手把手教你擼一個(gè)Mini JVM系列(1)之解析Class File -- 初探
手把手教你擼一個(gè)Mini JVM系列(2)之解析Class File -- 常量池
手把手教你擼一個(gè)Mini JVM系列(3)之解析Class File -- 字段袜啃、方法、屬性
手把手教你擼一個(gè)Mini JVM系列(4)之執(zhí)行引擎
手把手教你擼一個(gè)Mini JVM系列(6)之控制流 -- 條件判斷和循環(huán)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末幸缕,一起剝皮案震驚了整個(gè)濱河市群发,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌发乔,老刑警劉巖熟妓,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異栏尚,居然都是意外死亡起愈,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門译仗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抬虽,“玉大人,你說我怎么就攤上這事纵菌〔郏” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵咱圆,是天一觀的道長笛辟。 經(jīng)常有香客問我,道長序苏,這世上最難降的妖魔是什么手幢? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮杠览,結(jié)果婚禮上弯菊,老公的妹妹穿的比我還像新娘。我一直安慰自己踱阿,他們只是感情好管钳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著软舌,像睡著了一般才漆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上佛点,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天醇滥,我揣著相機(jī)與錄音,去河邊找鬼超营。 笑死鸳玩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的演闭。 我是一名探鬼主播不跟,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼米碰!你這毒婦竟也來了窝革?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤吕座,失蹤者是張志新(化名)和其女友劉穎虐译,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吴趴,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡漆诽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锣枝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厢拭。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖惊橱,靈堂內(nèi)的尸體忽然破棺而出蚪腐,到底是詐尸還是另有隱情,我是刑警寧澤税朴,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布回季,位于F島的核電站,受9級(jí)特大地震影響正林,放射性物質(zhì)發(fā)生泄漏泡一。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一觅廓、第九天 我趴在偏房一處隱蔽的房頂上張望鼻忠。 院中可真熱鬧,春花似錦杈绸、人聲如沸帖蔓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽塑娇。三九已至澈侠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間埋酬,已是汗流浹背哨啃。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留写妥,地道東北人拳球。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像珍特,于是被迫代替她去往敵國和親祝峻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容