深入解析Class類檔案的結構

猿奮發表於2019-03-24

前言

要深入學習Java以及Java虛擬機器,深入學習Java位元組碼檔案是繞不開的一條路,只有知道了位元組碼檔案裡的排列結構,你才能透徹的瞭解在JVM裡,類載入是怎麼載入Java類的,是怎麼將二進位制流轉化為執行時資料結構的。

Class檔案是是一組以8位元組為基礎單位的二進位制流,各個資料專案嚴格按照順序緊湊地排列在Class檔案中,中間沒有任何分隔符。

這裡的Class檔案其實不是特指Java的位元組碼檔案,任何程式語言的編譯器只要按照位元組碼檔案規範編譯成Class檔案,都可以在JVM上執行,所以位元組碼檔案和JVM是和語言無關的。

另外一般Class檔案指的不一定是儲存在磁碟上的以.class字尾結束的檔案,是一種泛指,指的是一切按照位元組碼檔案規範排列的二進位制位元組流。

位元組碼檔案解析

Class檔案採用下面這種類似C語言的結構體的偽結構來儲存資料,整個Class檔案是一張表,表裡又由無符號數和表組成。

ClassFile { 
    u4  magic; // 魔數,固定為"0xCAFEBABY"
    u2  minor_version; //jdk次版本號
    u2  major_version;  //jdk主版本號
    u2  constant_pool_count;  //常量池陣列大小,從1計數
    cp_info  constant_pool[constant_pool_count - 1]; //常量池陣列
    u2  access_flags;  //類的訪問標誌,如:public
    u2  this_class;  //類索引,指向常量池中的類符號引用
    u2  super_class;  //父類索引,指向常量池中的類符號引用
    u2  interfaces_count; //實現的介面的數量
    u2  interfaces[interfaces_count]; //介面列表,按implements後面的介面順序
    u2  fields_count;  //欄位數
    field_info  fields[fields_count]; //欄位表
    u2  methods_count; //方法數
    method_info  methods[methods_count]; //方法表
    u2  attributes_count; //屬性表大小
    attribute_info  attributes[attributes_count]; //屬性表
}

複製程式碼

從上面的偽結構可以看到,Class檔案根據上面的順序把規定的資料型別按照佔用的位元組依次排列下來。

下面通過一個例子來實戰分析一下Class檔案

//Test.class
public class Test { 
    public static int a = 1;
    public static final int b = 1; 
    public void say(){
        System.out.println("Hello");
    }
}
複製程式碼

位元組碼檔案實戰分析
上圖是編譯後的Test.class檔案的二進位制資料,可以按照上面ClassFile的結構順序依次分析下,下面是部分分析結果:
(1) u4 magic
    4個位元組(000h:0123)魔數: 0xCAFEBABY

(2) u2 minor_version
    2個位元組(000h:45)次版本號: 0x0000, 次版本號為0

(3) u2 major_version
    2個位元組(000h:67)主版本號: 0x0034,即52,JDK1.0-1.1:45.0 ~ 45.3, 1.1後版本增1,數字加1,所以這裡用的是1.1 + 0.(52-45) = 1.8

(4) u2 constant_pool_count
    2個位元組(000h:89)常量池大小:0x0027,即39,常量池陣列是從1開始計數的,說明常量池中有38個常量,後面依次排列的就是常量池的38個常量

(5) cp_info constant_pool[constant_pool_count - 1]
    常量池所佔的位元組數是由常量池中常量的數量以及型別所決定的,這裡有38個常量,每個常量開頭都有一個位元組的tag標識常量的型別,具體型別可以參考最下面的腦圖,根據這個標識可以找到這個常量所佔的位元組以及含義,下面分析其中一個常量,其餘的讀者有興趣可以全部完成

  • 000h:a 0x0A,表示常量型別為10,查表可知是CONSTANT_Methodref方法符號引用,那接下來的四個位元組,前兩個位元組表示指向常量池中方法所在類的符號引用的索引項,就是常量池的陣列下標,所在的位置是方法所在類的符號引用
  • 000h:bc 0x0007,指向常量池陣列第7個元素,第7個常量是一個java.lang.Object類的符號引用
  • 000h:de 0x0018, 指向常量池陣列的第24個元素,第24個常量是一個名稱和型別的符號引用,方法名是<init>,描述符是()V

這樣第一個常量就分析完成,共佔5個位元組,表示的是方法符號引用,該方法所在的類是Object類,方法名稱是<init>, 無引數,返回值是void

藉助工具javap可以更直觀的看到我們剛剛分析的部分結果以及全部類檔案的結構,使用以下命令即可:

javap -v Test.class
複製程式碼

結果如圖:

javap.png

通過上面的圖可以看到,和我們上面的部分分析是一致的

Class檔案結構腦圖

下面是我在看《深入理解Java虛擬機器》這本書的時候整理的關於Class檔案結構的腦圖,圖片比較大,右鍵另存為圖片再檢視會更方便。

class.png

原文連結:www.jackielee.cn/posts/7eb7d…
更多幹貨請掃碼關注

程式猿隨記

相關文章