該文爲作者原創,請轉載者註明出處
以下爲一個Java類--Temp4Test
package com.demo;
public class Temp4Test extends Temp3Test {
private int i = 1;
public float f;
public static String thisstr = "";
public Temp4Test(int ii, String str, float ff) {
i = ii;
thisstr = str;
f = ff;
}
public static void main(String[] args) {
Temp4Test t4 = new Temp4Test(100, "hello", 5.5f);
System.out.println(t4);
}
@Override
public String toString() {
return "[" + i + " , " + thisstr + " , " + f + "]";
}
}
其父類Temp3Test.java的實現爲:
package com.demo;
public class Temp3Test implements Cloneable {
private int ii;
public static String str = "hi";
public static void main(String[] args) {
int i = 123;
}
}
所生成的Temp4Test.class的二進制文件爲:
感興趣的同學可以自己編譯下就可以看到上述結果。
下面逐位分析一下二進制文件中各位的含義
a.象徵是.class文件的魔數:頭4個Byte,看到這4個Byte就可以基本確認爲一個.class文件,固定值:0xCAFEBABE。
b.class文件版本號,第5、6個字節是次版本號,第7、8個字節爲主版本號,在上述二進制碼爲0x00000034,代表版本號爲52.0即JDK1.8.0
c.常量池,從0x0052開始(含0x0052)
c.1 首先的兩個字節爲常量池中所含常量的數量,本例中即爲0x0052,所代表的數爲:0x0052==5*16+2-1==81(換爲十進制爲81個常量)
c.2 從0x07開始爲1-81個常量的二進制位表,開始位如下圖所示:
常量池共有14種類型的常量,如下圖所示,截圖來源於《深入理解Java虛擬機》
每一個常量,第一位均爲該常量類型,即上述表中14項之一,以第一個常量爲例0x07表示類或接口的符號引用,即CONSTANT_Class_info,此類型結構爲:
因此第一個常量即爲0x070002,0x0002是一個索引,表示指向第二個常量,第二個常量類型爲0x01,爲下圖所示二進制位
0x01表示(上面有表)CONSTANT_Utf8_info,即字符串,CONSTANT_Utf8_info類型的常量結構爲下圖:
可知,第二個常量對應的二進制碼爲:0x010012(共計1*16+2=18位)636F6D2F64656D6F2F54656D703454657374,後面的字符串所對應的文本爲:com/demo/Temp4Test,以此類推,可以推出上述二進制文件中的所有常量池常量,以下是我手寫的一個推導圖,有點粗漏,^_^
以下爲14種常量項的結構總表
d.訪問標誌,緊挨着常量池的兩個字節,含義如下表:
在上述示例中爲:
0x0021==0x0001|0x0020表示是一個由用戶定義的public的類
e.接下來的二進制表示該類的繼承關係,共有三項內容:
e.1 類索引,兩個字節,常量池中的對本類的描述
e.2 父類索引,兩個字節,常量池中對父類的描述
e.3 接口索引,頭兩個字節表示接口數,然後,緊跟進接口列表
本例中的二進制碼爲:
0x0001表示常量池中第一個常量爲類索引,0x0003爲表示常量池中第三個常量爲父類索引,0x0000表示實現的接口個數爲0個
f.接着的二進制表示字段表,頭兩位爲個數,從上圖可以看出,有三個字段(0x0003),每個字段規則如下圖
f.1 access_flags的取值如下圖
f.2 name_index的含義,同上,映射至常量池中的常量索引
f.3 descriptor_index,描述符,表示該變量的類型,此處插一下描述符的表示規則:
f.3.* 描述符中的字符含義如下:
f.3.** 表示數組時,每一個維度前用“[”表示
f.3.*** 描述方法,先參後返回值
下面舉一個描述符的例子:
上面的例子中,Temp4Test.java類中的構造函數的返回值及參數描述爲:(ILjava/lang/String;F)V
本例中的字段表,三個字段分別爲:
0x0002(private)00050006(查看第五常量、第六常量)0000
0x0001(public)00070008(查看第七常量、第八常量)0000
0x0009(0x0001|0x0008 public static)0009000A(查看第九常量、第十常量)0000
g.接着的二進制表示方法表,頭兩位爲個數,從上圖可以看出,有四個字段(0x0004),每個方法表述規則如下圖
g.1 access_flags的含義與字段表有所差別,具體如下圖:
其他的name_index、descriptor_index同字段表
g.2 在本例中,方法1-方法4的二進制塊如下圖:
方法1:
兩個紅色豎線中間的部分
方法2:
方法3:
方法4:
g.3 下面找一個方法來說明一下方法的二進制怎麼看,以方法2爲例
0x0001 -- public
0x0014 -- 第二十個常量,方法名
0x0015 -- 第二十一個常常,方法描述
0x0001 -- 含有一個屬性表
後面一直到方法結束均爲屬性表的二進制內容
g.4 屬性是一個特別複雜的二進制規則,之後會寫一篇文章專門說述一下屬性表的讀法,在此不一一展開描述,只說一下當前例子方法(即方法2)中的屬性表讀取規則
0x000D -- 第十三個常量,查常量表,可知,表示該屬性爲Code,Code的屬性規則爲:
attribute_name_index,即0x000D,表示Code
attribute_length,表示該屬性所佔字節數(不包括attribute_name_index和attribute_length),方法2中爲0x00000074==7*16+4==116
max_stack,最大堆棧數爲0x0002
max_locals,最大臨時變量數爲0x0004
code_length,字節碼數量爲0x00000018 == 16+8 == 24Byte
code,如下圖:
字節碼的閱讀不在該文中討論
exception_table_length,0x0000,沒有
attributes_count,0x0002,下面內嵌了兩個屬性表,內嵌的屬性表的讀取方式同上面所述,和外層的讀取規則是一樣的,只是屬性不爲Code了。因屬性表的全部分類並未羅列,也就不再讀了從0x0012開始直到方法2結束,均爲這兩個屬性表的二進制碼。
h.方法表的二進制碼結束後,對於本例來講,基本二進制碼就進入最後了,還剩一點是SourceFile的屬性,讀法,如下:
對應的二進制爲:
0x0050,第八十位常量--SourceFile
0x00000002,後面還有兩位,這個值在SourceFile類型中是定值,只有2位,原因就是上圖的規則定義
0x0051,第八十一位常量
結語:終於把類的二進制文件讀完了,當然,上述例子比較簡單,但麻雀雖小五臟俱全,複雜的文件只是多了一些其他類別,讀法是和上述例子一致的。上面有一些內容將在後續文章中繼續詳述,會有一篇文章專門講述文本化的常量池是如何讀的,還會有一篇文章講述屬性表,再有一篇文章詳述字節碼,文中的截圖來源於《深入理解Java虛擬機》,在此向作者致以最深的敬意,同時,圖片若侵權,請聯繫我,將第一時間刪除。
比二進制更方便的是文本形態的閱讀方法,詳見第二篇:Java中的類文件結構之二:分析一個.class文件的文本化閱讀