Java Class檔案結構例項分析(上)
本文假定讀者對Java Class檔案格式有一些基本的瞭解,建議結合相關書籍進行對照閱讀。
Class檔案格式資訊


例項程式碼
package chapter6; public class TestClass { private int m; public int inc() { return m + 1; } } 複製程式碼
使用JDK1.8編譯成class檔案,然後通過WinHex開啟

魔數(magic)
型別:u4
位元組地址:00000000~00000003
值:0xCAFEBABE

Class檔案版本
次版本號(minor_version)
型別:u2
位元組地址:00000004~00000005
值:0x0000
主版本號(major_version)
型別:u2
位元組地址:00000006~00000007
值:0x0034
將0x0034轉換為十進位制,計算得到52,對應版本號為JDK 1.8。

常量池
常量池容量計數值(constant_pool_count)
型別:u2
位元組地址:00000008~00000009
值:0x0016
將0x0016轉換為十進位制,計算得到22。由於容量計數是從1開始(如果沒有特殊情況,通常都是從0開始),因此常量池中有21項常量,索引值範圍為1~21。

常量池中每一項常量都是一個表,表開始的第一位是一個u1型別的標誌位(tag)。
第1項常量
tag型別:u1
tag位元組地址:0000000A
tag值:0x07
查表可知這個常量屬於CONSTANT_Class_info結構,代表一個類或者介面的符號引用。
name_index型別:u2
name_index位元組地址:0000000B~0000000C
name_index值:0x0002
0x0002指向了常量池中的第2項常量。

第2項常量
tag型別:u1
tag位元組地址:0000000D
tag值:0x01
查表可知這個常量屬於CONSTANT_Utf8_info結構,代表一個UTF-8編碼的字串。

length型別:u2
length位元組地址:0000000E~0000000F
length值:0x0012
將0x0012轉換為十進位制,計算得到18。
bytes型別:u1
bytes位元組地址:00000010~00000021(length表明地址範圍為18個位元組)
bytes值:下方圖片淺藍底對應的所有位元組內容
通過WinHex檢視,對應內容為chapter6/TestClass,即類的全限定名。

通過逐個位元組對照ASCII字元表,我們同樣可以得到內容為chapter6/TestClass。
- 獲取ASCII字元表 :在Linux上執行 man ascii ,翻頁在Tables項可以看到字元表。
- 查詢字元:先找橫座標,再找縱座標,橫豎交叉的位置即為位元組對應的字元。
例如0x63為c,0x68為h,0x61為a,0x70為p,0x74為t,0x65為e,0x72為r,連起來代表單詞chapter。

第3項常量
tag型別:u1
tag位元組地址:00000022
tag值:0x07
這個常量屬於CONSTANT_Class_info結構,代表一個類或者介面的符號引用。
name_index型別:u2
name_index位元組地址:00000023~00000024
name_index值:0x0004
0x0004指向了常量池中的第4項常量。
第4項常量
tag型別:u1
tag位元組地址:00000025
tag值:0x01
這個常量屬於CONSTANT_Utf8_info結構,代表一個UTF-8編碼的字串。
length型別:u2
length位元組地址:00000026~00000027
length值:0x0010
將0x0010轉換為十進位制,計算得到16。
bytes型別:u1
bytes位元組地址:00000028~00000037(length表明地址範圍為16個位元組)
bytes值:下方圖片淺藍底對應的所有位元組內容
通過WinHex檢視,對應內容為java/lang/Object,即類的全限定名。

第5項常量
tag型別:u1
tag位元組地址:00000038
tag值:0x01
這個常量屬於CONSTANT_Utf8_info結構,代表一個UTF-8編碼的字串。
length型別:u2
length位元組地址:00000039~0000003A
length值:0x0001
bytes型別:u1
bytes位元組地址:0000003B(length表明地址範圍為1個位元組)
bytes值:0x6D
通過WinHex檢視,對應內容為例項變數m。

其他常量可以通過類似的方法進行分析,但這樣一個個分析確實挺辛苦的。
其實,JDK已經為我們提供了一個Class檔案位元組碼工具:javap,可以讓我們較為直觀的看到Class檔案的位元組碼內容。
執行命令: javap -verbose TestClass.class ,擷取常量池部分內容如下:

可以看到,版本號及前5個常量與我們分析的結果是一致的。所以,能用1行程式碼搞定的事兒,就不要用2行(浪費筆墨)。
常量池最後一個位元組:000000D8
訪問標誌(access_flags)
型別:u2
位元組地址:000000D9~000000DA
值:0x0021
檢視類或介面訪問標誌含義表可知,該類的訪問標誌為ACC_PUBLIC(0x0001)、ACC_SUPER(0x0020)。

另外,通過類的定義public class TestClass,同樣可以推斷出類的訪問標誌為ACC_PUBLIC、ACC_SUPER,而ACC_INTERFACE、ACC_ENUM、ACC_FINAL、ACC_ABSTRACT、ACC_ANNOTATION、ACC_SYNTHETIC都可以排除。
所以,access_flags應該為0x0001|0x0020=0x0021,結果與檢視位元組碼相同。
類索引(this_class)
型別:u2
位元組地址:000000DB~000000DC
值:0x0001
this_class指向常量池的第1個常量,基於前面的分析可知:
- 第1個常量的型別為Class,Class名稱索引指向第2個常量。
- 第2個常量型別為Utf8,對應內容為chapter6/TestClass。
因此,類索引(this_class)指向的類為chapter6/TestClass。
父類索引(super_class)
型別:u2
位元組地址:000000DD~000000DE
值:0x0003
同樣,super_class指向常量池的第3個常量。
- 第3個常量的型別為Class,Class名稱索引指向第4個常量。
- 第4個常量型別為Utf8,對應內容為java/lang/Object。
因此,父類索引(super_class)指向的類為java/lang/Object。
介面計數器(interfaces_count)
型別:u2
位元組地址:000000DF~000000E0
值:0x0000
介面計數器值為0,說明該類沒有實現任何介面。
介面表(interfaces)
無
類索引(this_class)、父類索引(super_class)和介面索引(interfaces)這三項資料共同確定了當前類以及其繼承關係,相關常量池內容如下:

完整地址範圍:000000DB~000000E0
