這裏給大家介紹一款字節碼分析小工具——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 |
|
方法表的具體解析
知道方法表的組成結構,我們就可以直接對照着字節碼文件去解析了。依然是前文用的java代碼示例產生的字節碼文件。緊接在『字段表』後面的16進制是0×0004=4。即該類有4個成員方法!看源代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
源代碼中,我們只定義了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 |
|
所以,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 |
|
- Java程序方法體中的代碼經過Java編譯處理後,最終變成字節碼指令存儲在Code屬性中。Code屬性出現在方法表的屬性集合中,但是並非所有方法都有這個屬性。例如接口或者類中的抽象方法就不存在Code屬性。
Code屬性其實是一個結構比較複雜的屬性表。這裏就不做過多描述,打算後面抽個時間用一篇博客來說說它。其實方法表的<init>方法到此,分析得差不多了。接下來,大家可以再對照一遍,自己去把每個方法都分析一遍,加深印象。
歡迎工作一到五年的Java工程師朋友們加入Java程序員開發: 854393687
羣內提供免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!