《深入理解Java虛擬機》讀書筆記2-class文件結構
class文件結構
Class文件內容可以分為兩種數據類型:無符號數和表。其中無符號數包括u1,u2,u3,u4,分別代表1個字節,2個字節,3個字節和4個字節。無符號數可以表示數字、UTF8編碼的字符串。表是由多個無符號數或者其他表構成的數據結構,以_info結尾。可以看出Class文件的基礎單位是8位的字節,而當遇到多個字節構成數據項時,是按照高位在前的順序排列(Big-Endian)。
Class文件的組成數據項按先後順序如下表所示:
類型 |
名稱 |
數量 |
u4 |
magic魔數 |
1 |
u2 |
minor_version次版本號 |
1 |
u2 |
major_version主版本號 |
1 |
u2 |
Constant_pool_count常量池計數 |
1 |
cp_info |
constant常量池 |
Constant_pool_count |
u2 |
Access_flag訪問標誌 |
1 |
u2 |
This_class類索引 |
1 |
u2 |
Super_class父類索引 |
1 |
u2 |
Interfaces_count接口索引計數 |
1 |
u2 |
interfaces接口池 |
Interfaces_count |
u2 |
Fields_count 字段表計數 |
1 |
field_info |
Fields |
Fields_count |
u2 |
Attributes_count 字段屬性表計數 |
1 |
attribute_info |
Attributes |
Attributes_count |
u2 |
Methods_count方法表計數 |
1 |
method_info |
methods方法池 |
Methods_count |
u2 |
Attributes_count 方法屬性表計數 |
1 |
attribute_info |
Attributes |
Attributes_count |
u2 |
Attributes_count 類屬性表計數 |
1 |
attribute_info |
Attributes |
Attributes_count |
Class文件按照上表的數據項順序緊湊地排序,無分隔符。(其中屬性表是可能存在,若無相關屬性時則不存在屬性表集合)
可以使用jdk自帶的分析class文件字節碼工具javap將class文件轉化為字符形式,如javap –verbose Test.class。如:
1 magic魔數
魔數的作用是標識文件格式,之所以不用文件後綴名是因為文件後綴名容易被更改。Class文件的前四個字節即魔數,十六進制固定為:0xCAFEBABE(咖啡寶貝)。
2 version版本號
class文件中緊接魔數的是2字節的次版本號和 2字節的主版本號。java主版本號從45開始,jdk1.1之後的每個大版本依此加1(如jdk2為46,jdk7為51),次版本號為0~65535。Java虛擬機嚴格要求只能執行不能超過其版本的class文件,比如jdk1.1只能執行45.0~45.65535的class文件,jdk7只能支持45.0~51.65535的class文件。
3 constants_pool常量
Class文件中,相同數據項的集合都是以一個容量計數值,加上該計數值個數據項組成。比如常量池,首先是一個constants_pool_count,之後是constants_pool_count個constants。
常量池容量計數值count是一個u2無符號數,代表接下來真正的常量有count-1個,之這是因為規定常量池索引從1開始,不分配索引0則是為了滿足某些指向常量池的數據在特定情況下不需要引用任何一個常量的情況。
常量池是class文件的資源庫,存放了字面量、類和接口全限定名、字段名稱和描述符、方法名稱和描述符。總共有14種類型,每種類型都有不同的表結構。但統一的時每種表結構都是以一個u1類型的tag標誌位開始,代表該常量的類型。具體每種常量結構如下表所示:
4 access_flag訪問標誌
接下來是一個u2類型的訪問標誌位,該位有16位,目前只使用了8位,每一位根據0、1值代表當前類的不同訪問信息,剩余8位一律為0。具體標誌位及其含義如下圖所示:
標誌名稱 |
標誌值 |
含義 |
ACC_PUBLIC |
0x0001 |
是否位public類型 |
ACC_FINAL |
0x0010 |
是否被申明為final,只有類可設置 |
ACC_SUPER |
0x0020 |
是否允許使用invokespecial字節碼指令的新語義(該指令語義在jkd1.0.2發生過改變) |
ACC_INTERFACE |
0x0200 |
是否是接口 |
ACC_ABSTRACT |
0x0400 |
是否為abstract,對於接口和抽象類為真 |
ACC_SYNTHETIC |
0x1000 |
是否並非由用戶代碼產生 |
ACC_ANNOTATION |
0x2000 |
是否是註解 |
ACC_ENUM |
0x400 |
是否是枚舉 |
5 this_class類索引
接下來是一個u2類型的索引,指向常量池中代表這個類全限定名的CONSTANT_Class_info數據項。(全限定名就是把類全名中的.換為/)
6 super_class父類索引
父類索引類型同類索引,也是CONSTANT_Class_info。Java中除Object類以外,所有類都有父類,即super_class索引項不為0,而Object類的super_class索引項是0代表為空。
7 interfaces 接口索引集合
接口索引集合先有一個u2類型的計數值,代表後續有多少個接口索引,接口索引也是u2類型的常量池。接口順序是完全按照類implements(接口extends)語句之後的順序。
8 fields字段表集合
字段表集合同樣由一個fields_count字段表計數值加fields_count個字段表組成。Fields_count還是u2類型。字段表結構如下:
類型 |
名稱 |
數量 |
u2 |
Access_flags訪問標誌 |
1 |
u2 |
Name_index字段名索引 |
1 |
u2 |
Descriptor_index 描述符索引 |
1 |
u2 |
Attributes_count 屬性表計數值 |
1 |
Attribute_info |
Attributes屬性表 |
Attributes_count |
- l 首先是類似於類訪問標誌的一個u2類型的字段訪問標誌,目前有9位在用標誌位,具體含義如下圖所示:
標誌名稱 |
標誌值 |
含義 |
ACC_PUBLIC |
0x0001 |
是否public |
ACC_PRIVATE |
0x0002 |
是否private |
ACC_PROTECTED |
0x0004 |
是否protected |
ACC_STATIC |
0x0008 |
是否static |
ACC_FINAL |
0x0010 |
是否final |
ACC_VOLATILE |
0x0040 |
是否volatile |
ACC_SYNTHETIC |
0x1000 |
是否由編譯器產生 |
ACC_ENUM |
0x4000 |
是否enum |
- l 之後是字段簡單名稱,即沒有類型的字段名。
- l 描述符是指字段的類型、方法的參數和返回值。規定對於基礎類型以及代表返回值的void使用一個大寫字符標識(boolean類型用Z),對象類型用L加對象的全限定名,對於數字則使用[加數組元素類型標識。所以字段int m 的描述符為I。對於方法的描述符則規定按參數在前,返回類型在後的順序,參數使用()包含起來。所以方法int indexOf(char[] source,String str)描述符為([CS])I。
- l 屬性表集合,由屬性表計數值和屬性表計數值個屬性表組成,屬性表記錄內容為字段其他屬性,比如final static int m=123,就會有一個ConstantValue的屬性記錄常量值。屬性表內容見之後的屬性表集合講解。
此外,需要註意字段表不會包含父類的字段,但有可能列車原本java代碼中不存在的字段,比如內部類會添加訪問外部類的實例字段。
9 methods方法表集合
方法表集合記錄了類的方法集合,結構同字段表集合類似:Methods_count+methods。其中方法表的結構表示也相同,只是方法的access_flag訪問標識如下表所示:
標誌名稱 |
標誌值 |
含義 |
ACC_PUBLIC |
0x0001 |
是否public |
ACC_PRIVATE |
0x0002 |
是否private |
ACC_PROTECTED |
0x0004 |
是否protected |
ACC_STATIC |
0x0008 |
是否static |
ACC_FINAL |
0x0010 |
是否final |
ACC_SYNCHRONIZED |
0x0020 |
是否為synchronized |
ACC_BRIDGE |
0x0040 |
是否為編譯器產生的橋接方法 |
ACC_VARARGS |
0x0080 |
是否接受不定參數 |
ACC_NATIVE |
0x0100 |
是否為native |
ACC_ABSTRACT |
0x0400 |
是否為abstract |
ACC_STRICTFP |
0x0800 |
是否為strictfp |
ACC_SYNTHETIC |
0x1000 |
是否由編譯器產生 |
註意當子類沒有重寫父類方法時,不會出現父類的方法表。同時也有可能產生原java代碼中沒有的方法,如類初始化方法<clinit>,實例構造器<init>。方法也有屬性表集合。
有關屬性表集合見下一篇吧~~
《深入理解Java虛擬機》讀書筆記2-class文件結構