Java位元組碼結構剖析三:方法表
這裡給大家介紹一款位元組碼分析小工具—— ofollow,noindex" target="_blank">jclasslib bytecode viewer 。它可以將位元組碼檔案結構化的展現給我們看。
緊接著上篇『欄位表』的分析。後面的分析輪到了『方法表』。
方法表結構
- u2 method_count: 方法計數器,methods_count 的值表示當前 class 檔案 methods[]陣列的成員個數。
- method_info methods[methods_count]: 方法表,methods[]陣列中的每個成員都必須是一個 method_info 結構的資料項,用於表示當前類或介面中某個方法的完整描述。
- method_info 結構:
method_info { u2 access_flag1 u2 name_index1 u2 descriptor_index1 u2 attribute_count1 attribute_info attributes[attribute_count] }
方法表的具體解析
知道方法表的組成結構,我們就可以直接對照著位元組碼檔案去解析了。依然是前文用的java程式碼示例產生的位元組碼檔案。緊接在『欄位表』後面的16進位制是0×0004=4。即該類有4個成員方法!看原始碼:
public class MyTest2 { String str = "Welcome"; private int x = 5; public static Integer in = 10; public static void main(String[] args) { MyTest2 myTest2 = new MyTest2(); myTest2.setX(8); in = 20; } public void setX(int x) { this.x = x; } }
原始碼中,我們只定義了2個成員方法!但是位元組碼卻說有4個方法。我們用jclasslib小工具開啟看一下。展示如下:
這樣就一目瞭然了,其實位元組碼裡除了我們自己顯示定義的2個方法 main
和 setX
。Java編譯器生成位元組碼的時候預設又幫我們生成了2個方法 <init>
和 <clinit>
。
<init>方法就是預設的構造方法。我們知道,一個類必須要有至少一個構造方法,用來完成類的例項化過程。當我們沒有顯示去給一個類定義一個構造方法時,Java編譯器在為生成位元組碼檔案時,會預設給它生成一個預設的構造方法。
<clinit>方法是類的構造器。是類初始化階段要執行的方法,它的職責就是為類的靜態變數賦初始值(由程式員定義的那個初始值,在我們的原始碼中就是靜態變數in的初始值10),或者如果類中有靜態程式碼塊,那就並按順序執行靜態程式碼塊的程式碼。
所以,當一個類有靜態變數或者靜態程式碼塊的時候,Java編譯器會為這個類的位元組碼裡生成一個<clinit>方法,在類初始化階段去執行!
這就是為什麼程式碼中我們只定義了2個方法,但生成的位元組碼裡卻有4個方法的原因了。
分析第一個方法。先看看方法表的部分16進位制的資訊,如下:從0×004開始。
首先是方法的access_flag(訪問標誌位),即0×0001。說明此方法是public。接著是方法的name_index(指向常量池的索引,代表方法的全限定名稱),0×0011=17。我藉助jclasslib小工具可以查到方法名稱是<init>,就是Java編譯器預設生成的構造方法。然後,是該方法的描述符資訊descriptor_index,0×0012=18,同樣可以查到()V。這個描述符說明我們的方法是無參的『()』。且無返回值『V』。完美符合我們構造方法的定義。
屬性表分析
我們再看看它的屬性個數,attributes_count的項的值表示這個方法的附加屬性的數量。0×0001=1,說明這個方法只有一個附加屬性。那後面就是對屬性表的分析了。我們先看一下屬性表的結構:
attribute_info { u2 attribute_name_index; u4 attibute_length; u1 info[attibute_length] }
所以,0×0013=19。表示的就是這個屬性的名字在常量池中的索引。查閱得,該屬性的名字是『Code』。Code屬性很重要,因為Java程式方法中的程式碼經過javac編譯之後形成位元組碼存在了Code屬性內。在這裡,我們通過jclasslib先檢視一下Code屬性裡有什麼。
紅框裡的助記符就是 <init>
方法裡要執行的程式碼邏輯!
Code屬性
- code屬性的作用是儲存該方法的結構,如所對應的位元組碼。
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]; }
- Java程式方法體中的程式碼經過Java編譯處理後,最終變成位元組碼指令儲存在Code屬性中。Code屬性出現在方法表的屬性集合中,但是並非所有方法都有這個屬性。例如介面或者類中的抽象方法就不存在Code屬性。
Code屬性其實是一個結構比較複雜的屬性表。這裡就不做過多描述,打算後面抽個時間用一篇部落格來說說它。其實方法表的<init>方法到此,分析得差不多了。接下來,大家可以再對照一遍,自己去把每個方法都分析一遍,加深印象。