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等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章