Java Class檔案結構例項分析(上)

二進位制之路發表於2018-10-14

本文假定讀者對Java Class檔案格式有一些基本的瞭解,建議結合相關書籍進行對照閱讀。

Class檔案格式資訊

image

image

例項程式碼

package chapter6;
public class TestClass {
	private int m;
	public int inc() {
		return m + 1;
	}
}
複製程式碼

使用JDK1.8編譯成class檔案,然後通過WinHex開啟

image

魔數(magic)

型別:u4
位元組地址:00000000~00000003
值:0xCAFEBABE

image

Class檔案版本

次版本號(minor_version)

型別:u2
位元組地址:00000004~00000005
值:0x0000

主版本號(major_version)

型別:u2
位元組地址:00000006~00000007
值:0x0034

將0x0034轉換為十進位制,計算得到52,對應版本號為JDK 1.8。

image

常量池

常量池容量計數值(constant_pool_count)

型別:u2
位元組地址:00000008~00000009
值:0x0016

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

image

常量池中每一項常量都是一個表,表開始的第一位是一個u1型別的標誌位(tag)。

第1項常量

tag型別:u1
tag位元組地址:0000000A
tag值:0x07

查表可知這個常量屬於CONSTANT_Class_info結構,代表一個類或者介面的符號引用。

name_index型別:u2
name_index位元組地址:0000000B~0000000C
name_index值:0x0002

0x0002指向了常量池中的第2項常量。

image

第2項常量

tag型別:u1
tag位元組地址:0000000D
tag值:0x01

查表可知這個常量屬於CONSTANT_Utf8_info結構,代表一個UTF-8編碼的字串。

image

length型別:u2
length位元組地址:0000000E~0000000F
length值:0x0012

將0x0012轉換為十進位制,計算得到18。

bytes型別:u1
bytes位元組地址:00000010~00000021(length表明地址範圍為18個位元組)
bytes值:下方圖片淺藍底對應的所有位元組內容

通過WinHex檢視,對應內容為chapter6/TestClass,即類的全限定名。

image

通過逐個位元組對照ASCII字元表,我們同樣可以得到內容為chapter6/TestClass。

  • 獲取ASCII字元表:在Linux上執行man ascii,翻頁在Tables項可以看到字元表。
  • 查詢字元:先找橫座標,再找縱座標,橫豎交叉的位置即為位元組對應的字元。

例如0x63為c,0x68為h,0x61為a,0x70為p,0x74為t,0x65為e,0x72為r,連起來代表單詞chapter。

image

第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,即類的全限定名。

image

第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。

image

其他常量可以通過類似的方法進行分析,但這樣一個個分析確實挺辛苦的。

其實,JDK已經為我們提供了一個Class檔案位元組碼工具:javap,可以讓我們較為直觀的看到Class檔案的位元組碼內容。

執行命令:javap -verbose TestClass.class,擷取常量池部分內容如下:

image

可以看到,版本號及前5個常量與我們分析的結果是一致的。所以,能用1行程式碼搞定的事兒,就不要用2行(浪費筆墨)。

常量池最後一個位元組:000000D8

訪問標誌(access_flags)

型別:u2
位元組地址:000000D9~000000DA
值:0x0021

檢視類或介面訪問標誌含義表可知,該類的訪問標誌為ACC_PUBLIC(0x0001)、ACC_SUPER(0x0020)。

image

另外,通過類的定義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)這三項資料共同確定了當前類以及其繼承關係,相關常量池內容如下:

image

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

image

欄位

欄位計數器(fields_count)

型別:u2
位元組地址:000000E1~000000E2
值:0x0001

說明當前類有1個欄位。

欄位表(fields)

image

image

訪問標誌(access_flags)

型別:u2
位元組地址:000000E3~000000E4
值:0x0002

對應的訪問標誌為ACC_PRIVATE。

名稱索引(name_index)

型別:u2
位元組地址:000000E5~000000E6
值:0x0005

對應常量池中的第5項常量,即欄位名為m。

描述符(descriptor_index)

型別:u2
位元組地址:000000E7~000000E8
值:0x0006

對應常量池中的第6項常量,值為I,即int型別。

因此,該欄位的定義為private int m;

image

屬性計數器(attributes_count)

型別:u2
位元組地址:000000E9~000000EA
值:0x0000

說明該欄位沒有屬性資訊。

屬性表(attributes)

無。

欄位完整地址範圍:000000E1~000000EA

image

最後是方法和屬性,由於內容複雜度及篇幅原因,我們下篇再續。


參考

《Java虛擬機器規範》(Java SE 8版)

《深入理解Java虛擬機器 JVM高階特性與最佳實踐》

個人公眾號

更多文章,請關注公眾號:二進位制之路

二進位制之路

相關文章