1. 程式人生 > >Java位元組碼結構剖析三:方法表

Java位元組碼結構剖析三:方法表

這裡給大家介紹一款位元組碼分析小工具——jclasslib bytecode viewer。它可以將位元組碼檔案結構化的展現給我們看。

緊接著上篇『欄位表』的分析。後面的分析輪到了『方法表』。

方法表結構

  • u2 method_count:方法計數器,methods_count 的值表示當前 class 檔案 methods[]陣列的成員個數。
  • method_info methods[methods_count]: 方法表,methods[]陣列中的每個成員都必須是一個 method_info 結構的資料項,用於表示當前類或介面中某個方法的完整描述。
  • method_info 結構:

1

2

3

4

5

6

7

method_info {

    u2 access_flag                     1

    u2 name_index                      1

    u2 descriptor_index                1

    u2 attribute_count                 1

    

attribute_info attributes[attribute_count]

}

方法表的具體解析

知道方法表的組成結構,我們就可以直接對照著位元組碼檔案去解析了。依然是前文用的java程式碼示例產生的位元組碼檔案。緊接在『欄位表』後面的16進位制是0×0004=4。即該類有4個成員方法!看原始碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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,說明這個方法只有一個附加屬性。那後面就是對屬性表的分析了。我們先看一下屬性表的結構:

1

2

3

4

5

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屬性的作用是儲存該方法的結構,如所對應的位元組碼。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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>方法到此,分析得差不多了。接下來,大家可以再對照一遍,自己去把每個方法都分析一遍,加深印象。

歡迎工作一到五年的Java工程師朋友們加入Java程式設計師開發: 854393687

群內提供免費的Java架構學習資料(裡面有高可用、高併發、高效能及分散式、Jvm效能調優、Spring原始碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!