手把手教你擼一個Mini JVM系列(3)之解析Class File -- 字段、方法、屬性

引子:

在常量池, 訪問修飾符, 類和接口后面緊跟的內(nèi)容是字段和方法, 這兩個結(jié)構(gòu)是最復(fù)雜的, 因為其里面包含有屬性這個成員, 而屬性又是可以嵌套的.

1. 字段

和之前的常量池一樣, 因為每個class中字段的數(shù)量是不確定的, 所以字段部分的開頭兩個字節(jié)用于表示當(dāng)前class文件中的字段的個數(shù), 緊跟著的才是具體的字段.

先來看一下字段的結(jié)構(gòu)

    Field_Info {
        u2 access_flag;
        u2 name_index;
        u2 descriptor_index;
        u2 attribute_count;
        attribute_info attributes[attribute_count];
    }
  • access_flag表示該字段的訪問修飾符, 字段的訪問修飾符和類的表示方式相似, 但是具體的內(nèi)容不一樣

    字段的訪問標(biāo)識

    圖1-1 FIELD-ACCESS-FLAG
  • name_index指向常量池中的name_index索引的常量項

  • descriptor_index指向常量池中的descriptor_index索引的常量項

  • attribute_count表示該字段的屬性個數(shù)

  • attributes[attribute_count]表示該字段的具體的屬性

注意: 這里字段的descriptor代表的字段的類型, 但是類型不是寫代碼的時候int, String這樣整個單詞的, 它是一些字符的簡寫, 如下:

圖1-2 DESCRIPTOR

所以, 舉個例子如果字段是String類型, 那么它的descriptor就是Ljava/lang/Object;如果字段是int[][], 那么它的descriptor就是[[I

對于屬性的解釋放到和方法屬性一起

2. 方法

方法和字段一樣, 也需要有一個表示方法個數(shù)的字段, 同時這個字段后面緊跟的就是具體的方法

同樣, 來看一下方法的結(jié)構(gòu):

    Method_Info {
        u2 access_flag;
        u2 name_index;
        u2 descriptor_index;
        u2 attribute_count;
        attribute_info attributes[attribute_count]
    }
  • access_flag的意義和之前field一樣, 只不過取值不同, method的access flag可以取的值如下:

    圖1-3 METHOD-ACCESS-FLAG

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

  • name_index的意義和field的也一樣, 表示了方法的名稱

  • descriptor_index的意義和field也一樣, 只不過其表示方法不同, 讓我們來看一下它是如何表示的:

    method的descriptor由兩部分組成, 一部分是參數(shù)的descriptor, 一部分是返回值的descriptor, 所以method的descriptor的形式如下:

    ( ParameterDescriptor* ) ReturnDescriptor
    

    而參數(shù)的descriptor就是field的descriptor. 返回值的descriptor也是field的descriptor但是多了一個類型就是void類型, 其的descriptor如下

        VoidDescriptor:V
    

    所以舉個例子, 如果一個方法的簽名是

    Object m(int i, double d, Thread t) {..}
    

    那么它的descriptor就是

    (IDLjava/lang/Thread;)Ljava/lang/Object;
    
  • attribute_count的意義和field一樣表示屬性的個數(shù)

  • attributes[attribute_count]和field也一樣表示具體的屬性, 屬性的個數(shù)由attribute_count決定

3. 屬性

3.1 屬性結(jié)構(gòu)

屬性這個數(shù)據(jù)結(jié)構(gòu)可以出現(xiàn)在class文件, 字段表, 方法表中. 有些屬性是特有的, 有些屬性是三個共有的.

屬性的描述如下:

ATTRIBUTE1
ATTRIBUTE2
ATTRIBUTE

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

這里我不會詳細(xì)解釋每一個屬性, 我只解釋一個對于實現(xiàn)mini jvm最重要的屬性, Code Attribute, 為什么說它重要, 因為我們的函數(shù)的代碼就是在Code Attribute中(實際上存儲的是指令). 其他屬性的一些解釋可以參考oracle的jvm規(guī)范中的描述

3.2 Code Attribute

首先來看一下Code Attribute的結(jié)構(gòu)

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

可以看到Code Attribute屬性是非常復(fù)雜的, 下面解釋一些每個成員的意義.

  • attribute_name_index指向的常量池中常量項的索引, 而且這個常量項的類型必須是UTF8 Info, 值必須是"Code"
  • attribute_length表示這個屬性的長度, 但是不包括開始的6個字節(jié)
  • max_stack表示Code屬性所在的方法在運行時形成的函數(shù)棧幀中的操作數(shù)棧的最大深度(真是不得不佩服java編譯器, 連一個函數(shù)運行時需要的操作數(shù)棧的深度都可以計算的出來)
  • max_locals表示最大局部變量表的長度
  • code_length表示Code屬性所在的方法的長度(這個長度是方法代碼編譯成字節(jié)后字節(jié)的長度)
  • code[length]表示的就是具體的代碼, 所以說java函數(shù)的代碼長度是有限制的, 編譯出來的字節(jié)指令的長度只能是4個字所能代表的最大值. 所以一個函數(shù)的代碼不能太長, 否者是不能編譯的.
  • exception_table_length表示方法會拋出的異常數(shù)量
  • exception_table[exception_table_length]表示具體的異常
  • attributes_count表示Code屬性中子屬性的長度, 之所以說屬性復(fù)雜就是因為屬性中還可以嵌套屬性
  • attributes[attributes_count]代表具體的屬性

現(xiàn)在來直觀的看一下Code Attribute的組成

圖1-5 CODE-ATTRIBUTE3

3.3 Code Attribute的兩個子屬性

Code Attribute中的兩個子屬性對于這次的mini jvm的實現(xiàn)可能不是很重要, 但是它們對于調(diào)試程序是非常重要的, 不知道大家有沒有想過為什么我們用IDE運行程序出錯時, IDE可以準(zhǔn)確的定位到是哪一行代碼出錯了? 為什么我們在斷點調(diào)試的時候可以看到每一個變量的值? 很關(guān)鍵的原因就在于Code屬性的這兩個子屬性.

3.3.1 LineNumberTable

LineNumberTable的結(jié)構(gòu)

LineNumberTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 line_number_table_length;
    {   u2 start_pc;
        u2 line_number; 
    } line_number_table[line_number_table_length];
}

我們著重要看的是line_number_table這個成語, 可以看到這個成員表示的就是字節(jié)碼指令和源碼的對應(yīng)關(guān)系, 其中start_pc是Code Attribute中的code[]數(shù)組的索引值, line_number是源文件的行號

3.3.1 LocalVariableTable

LocalVariableTable的結(jié)構(gòu)

LocalVariableTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_table_length;
    {   u2 start_pc;
        u2 length;
        u2 name_index;
        u2 descriptor_index;
        u2 index;
    } local_variable_table[local_variable_table_length];
}

其中最關(guān)鍵的成員大家也可以想到, 肯定是local_variable_table[local_variable_table_length]

  • start_pc和length表示局部變量的索引范圍([start_pc, start_pc + length))
  • name_index表示變量名在常量池中的索引
  • descriptor_index表示變量描述符在常量池中的索引
  • index表示此局部變量在局部變量表中的索引

4. 總結(jié)

至于對字段, 方法, 屬性的解析的代碼實現(xiàn)這里就不描述了, 大家直接看代碼實現(xiàn)吧.
到這篇為止, class文件的結(jié)構(gòu)和解析已經(jīng)全部介紹完了, 接下來的就是運行程序了, 也就是實現(xiàn)一個執(zhí)行引擎了, 所以接下來的才是整個jvm的重點.

5. 代碼地址

6. 本系列其他文章

手把手教你擼一個Mini JVM系列(1)之解析Class File -- 初探
手把手教你擼一個Mini JVM系列(2)之解析Class File -- 常量池
手把手教你擼一個Mini JVM系列(4)之執(zhí)行引擎
手把手教你擼一個Mini JVM系列(5)之源碼分析 -- 常量池、訪問標(biāo)志、類索引
手把手教你擼一個Mini JVM系列(6)之控制流 -- 條件判斷和循環(huán)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容