引子:
在常量池, 訪問修飾符, 類和接口后面緊跟的內(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這樣整個單詞的, 它是一些字符的簡寫, 如下:

所以, 舉個例子如果字段是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文件, 字段表, 方法表中. 有些屬性是特有的, 有些屬性是三個共有的.
屬性的描述如下:



<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的組成

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)

