每一個Class都對應着唯一的一個類或借口的定義信息。這里,我們稱為"Class文件格式"只是通俗的將任意一個符合有效的類或借口的格式這么稱呼,但是它並不一定是以磁盤文件的形式存在。
每個Class文件都是由8字節為單位的字節流組成,所有的16位、32位和64位長度的數據將被構造成 2個、4個和8個8字節單位來表示。
ClassFile結構
每一個Class文件對應於一個如下所示的ClassFile結構體。
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count; field_info fields[fields_count];
u2 methods_count; method_info methods[methods_count];
u2 attributes_count; attribute_info attributes[attributes_count];
}
其中u1、u2、u4分別代表1、2、4個字節無符號數。
magic:
魔數,魔數的唯一作用是確定這個文件是否為一個能被虛擬機所接受的Class文件。魔數值固定為0xCAFEBABE,不會改變。
minor_version、major_version:
分別為Class文件的副版本和主版本。它們共同構成了Class文件的格式版本號。不同版本的虛擬機實現支持的Class文件版本號也相應不同,高版本號的虛擬機可以支持低版本的Class文件,反之則不成立。
constant_pool_count:
常量池計數器,constant_pool_count的值等於constant_pool表中的成員數加1。
constant_pool[]:
常量池,constant_pool是一種表結構,它包含Class文件結構及其子結構中引用的所有字符串常量、類或接口名、字段名和其它常量。常量池不同於其他,索引從1開始到constant_pool_count -1。
access_flags:
訪問標志,access_flags是一種掩碼標志,用於表示某個類或者接口的訪問權限及基礎屬性。access_flags的取值范圍和相應含義見下表:
this_class:
類索引,this_class的值必須是對constant_pool表中項目的一個有效索引值。constant_pool表在這個索引處的項必須為CONSTANT_Class_info類型常量,表示這個Class文件所定義的類或接口。
super_class:
父類索引,對於類來說,super_class的值必須為0或者是對constant_pool表中項目的一個有效索引值。如果它的值不為0,那constant_pool表在這個索引處的項必須為CONSTANT_Class_info類型常量,表示這個Class文件所定義的類的直接父類。當然,如果某個類super_class的值是0,那么它必定是java.lang.Object類,因為只有它是沒有父類的。
interfaces_count:
接口計數器,interfaces_count的值表示當前類或接口的直接父接口數量。
interfaces[]:
接口表,interfaces[]數組中的每個成員的值必須是一個對constant_pool表中項目的一個有效索引值,它的長度為interfaces_count。每個成員interfaces[i] 必須為CONSTANT_Class_info類型常量。
fields_count:
字段計數器,fields_count的值表示當前Class文件fields[]數組的成員個數。
fields[]:
字段表,fields[]數組中的每個成員都必須是一個fields_info結構的數據項,用於表示當前類或接口中某個字段的完整描述。
methods_count:
方法計數器,methods_count的值表示當前Class文件methods[]數組的成員個數。
methods[]:
方法表,methods[]數組中的每個成員都必須是一個method_info結構的數據項,用於表示當前類或接口中某個方法的完整描述。
attributes_count:
屬性計數器,attributes_count的值表示當前Class文件attributes表的成員個數。
attributes[]:
屬性表,attributes表的每個項的值必須是attribute_info結構。
下面舉個簡單的例子來說明一下ClassFile的結構:
public class HelloWorld
{
String str = "";
public String getStr()
{
return str;
}
public void setStr(String str)
{
this.str = str;
}
}
通過javap工具我們能看到這個簡單的類的結構,如下:
我們可以看到一些信息包括主副版本號、常量池、ACC_FLAGS等,再來打開Class文件看一下:
根據前面所述的ClassFile結構,我們來分析下:
可以看到前4個字節為魔數,也就是0xCAFEBABE,這里都是十六進制。
魔數后2個字節為副版本號,這里副版本號是0.
再后2個字節是主版本號0x0033,轉為十進制,主版本號是51,和Javap工具所看到的一樣,這里我用的JDK版本是1.7。
這兩個字節是常量池計數器,常量池的數量為0x0017,轉為十進制是23,也就是說常量池的索引為1~22,這與Javap所看到的也相符。
常量池計數器后面就是常量池的內容,我們根據javap所看到的信息找到最后一個常量池項java/lang/Object,在字節碼中找到對應的地方:
常量池后面兩個字節是訪問標志access_flags:
值為0x0021,在javap中我們看到這個類的標志是
其中ACC_PUBLIC的值為0x0001,ACC_SUPER的值為0x0020,與字節碼是相匹配的。
至於ClassFile的其他結構,包括this_class、super_class、接口計數器、接口等等都可以通過同樣的方法進行分析,這里就不再多說了。
下面將詳細的介紹一下ClassFile結構中的中的各個部分。
常量池
所有的常量池項都具有如下通用格式:
cp_info
{
u1 tag;
u1 info[];
}
以1個字節的tag開頭,后面info[]項的內容tag由的類型所決定。tag有效的類型和對應的取值如下表:
下面我們來介紹下不同類型的tag所對應的結構和規則:
CONSTANT_Class_info結構:
CONSTANT_Class_info結構用於表示類或接口,格式如下:
CONSTANT_Class_info
{
u1 tag;
u2 name_index;
}
CONSTANT_Class_info結構的tag項的值為CONSTANT_Class(7)。name_index項的值,必須是對常量池的一個有效索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結構,代表一個有效的類或接口二進制名稱的內部形式。
CONSTANT_Fieldref_info, CONSTANT_Methodref_info和CONSTANT_InterfaceMethodref_info結構:
字段,方法和接口方法由類似的結構表示:
CONSTANT_Fieldref_info
{
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_Methodref_info
{
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_InterfaceMethodref_info
{
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_Fieldref_info結構的tag項的值為CONSTANT_Fieldref(9)。
CONSTANT_Methodref_info結構的tag項的值為CONSTANT_Methodref(10)。 CONSTANT_InterfaceMethodref_info結構的tag項的值為CONSTANT_InterfaceMethodref(11)
class_index項的值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Class_info結構,表示一個類或接口,當前字段或方法是這個類或接口的成員。
name_and_type_index項的值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_NameAndType_info結構,它表示當前字段或方法的名字和描述符。
CONSTANT_String_info結構:
CONSTANT_String_info用於表示java.lang.String類型的常量對象,格式如下:
CONSTANT_String_info
{
u1 tag;
u2 string_index;
}
CONSTANT_String_info結構的tag項的值為CONSTANT_String(8)。string_index項的值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示一組Unicode碼點序列,這組Unicode碼點序列最終會被初始化為一個String對象。
CONSTANT_Integer_info和CONSTANT_Float_info結構:
CONSTANT_Intrger_info和CONSTANT_Float_info結構表示4字節(int和float)的數值常量:
CONSTANT_Integer_info
{
u1 tag;
u4 bytes;
}
CONSTANT_Float_info
{
u1 tag;
u4 bytes;
}
CONSTANT_Integer_info結構的bytes項表示int常量的值,按照Big-Endian的順序存儲。 CONSTANT_Float_info結構的bytes項按照IEEE 754單精度浮點格式。表示float常量的值,按照Big-Endian的順序存儲。
CONSTANT_Long_info和CONSTANT_Double_info結構:
CONSTANT_Long_info和CONSTANT_Double_info結構表示8字節(long和double)的數值常量:
CONSTANT_Long_info
{
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
CONSTANT_Double_info
{
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
在Class文件的常量池中,所有的8字節的常量都占兩個表成員(項)的空間。如果一個CONSTANT_Long_info或CONSTANT_Double_info結構的項在常量池中的索引為n,則常量池中下一個有效的項的索引為n+2,此時常量池中索引為n+1的項有效但必須被認為不可用。
CONSTANT_Long_info結構的tag項的值是CONSTANT_Long(5)。 CONSTANT_Double_info結構的tag項的值是CONSTANT_Double(6)。
CONSTANT_Long_info結構中的無符號的high_bytes和low_bytes項用於共同表示long型常量,構造形式為((long) high_bytes << 32) + low_bytes,high_bytes和low_bytes都按照Big-Endian順序存儲。 CONSTANT_Double_info結構中的high_bytes和low_bytes共同按照IEEE 754雙精度浮點格式表示double常量的值。high_bytes和low_bytes都按照Big-Endian順序存儲。
CONSTANT_NameAndType_info結構:
CONSTANT_NameAndType_info結構用於表示字段或方法,但是和前面介紹的三個表示字段方法的結構不同,CONSTANT_NameAndType_info結構沒有標識出它所屬的類或接口,格式如下:
CONSTANT_NameAndType_info
{
u1 tag;
u2 name_index;
u2 descriptor_index;
}
CONSTANT_NameAndType_info結構的tag項的值為CONSTANT_NameAndType(12)。
name_index項的值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info結構,這個結構要么表示特殊的方法名<init>,要么表示一個有效的字段或方法的非限定名。
descriptor_index項的值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info(§4.4.7)結構,這個結構表示一個有效的字段描述符或方法描述符。
CONSTANT_Utf8_info結構:
CONSTANT_Utf8_info結構用於表示字符串常量的值:
CONSTANT_Utf8_info
{
u1 tag;
u2 length;
u1 bytes[length];
}
CONSTANT_Utf8_info結構的tag項的值為CONSTANT_Utf8(1)。length項的值指明了bytes[]數組的長度,bytes[]是表示字符串值的byte數組。
CONSTANT_MethodHandle_info結構:
CONSTANT_MethodHandle_info結構用於表示方法句柄,結構如下:
CONSTANT_MethodHandle_info
{
u1 tag;
u1 reference_kind;
u2 reference_index;
}
CONSTANT_MethodHandle_info結構的tag項的值為CONSTANT_MethodHandle(15)。reference_kind項的值必須在1至9之間(包括1和9),它決定了方法句柄的類型。
reference_index項的值必須是對常量池的有效索引,索引項和reference_kind的對應關系如下:
CONSTANT_MethodType_info結構:
CONSTANT_MethodType_info結構用於表示方法類型:
CONSTANT_MethodType_info
{
u1 tag;
u2 descriptor_index;
}
CONSTANT_MethodType_info結構的tag項的值為CONSTANT_MethodType(16)。descriptor_index項的值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示方法的描述符。
CONSTANT_InvokeDynamic_info結構:
CONSTANT_InvokeDynamic_info用於表示invokedynamic指令所使用到的引導方法、引導方法使用到動態調用名稱、參數和請求返回類型、以及可以選擇性的附加被稱為靜態參數的常量序列。
CONSTANT_InvokeDynamic_info
{
u1 tag;
u2 bootstrap_method_attr_index;
u2 name_and_type_index;
}
CONSTANT_InvokeDynamic_info結構的tag項的值為CONSTANT_InvokeDynamic(18)。bootstrap_method_attr_index項的值必須是對當前Class文件中引導方法表的bootstrap_methods[]數組的有效索引。ame_and_type_index項的值必須是對當前常量池的有效索引,常量池在該索引處的項必須是CONSTANT_NameAndType_info結構,表示方法名和方法描述符。
下面我們還是使用上面ClassFile的例子來簡單看下常量池:
通過javap我們看到常量池中第一項為:
是HelloWorld的初始化方法,再來看一下字節碼:
0A是第一個常量池項的tag,轉為十進制是10,查找上面的常量類型表的確是CONSTANT_Methodref類型常量。
根據CONSTANT_Methodref_info的結構,tag后2個字節為class_index,為常量池的某個可用索引,索引項必須為CONSTANT_Class_info結構:
0x0005,轉為十進制是5。索引位置5的常量為:
可以看到是Object類,Java中所有的類都是Object類的子類。HelloWorld類沒有顯示的定義構造方法,會自動調用父類Object的無參構造方法。
繼續看CONSTANT_Methodref_info的結構的第三個屬性name_and_type_index,為常量池的某個可用索引,索引項必須為CONSTANT_NameAndType_info結構:
0x0012,轉為十進制是18。索引位置18的常量為:
包括前面的索引5的CONSTANT_Class_info結構和這里的CONSTANT_NameAndType_info結構我們都可以繼續追蹤下去,這里我只是做簡單分析就不再往下了。
用同樣的方法可以分析常量池里每一個常量。
字段
每個字段(Field)都由field_info結構所定義,在同一個Class文件中,不會有兩個字段同時具有相同的字段名和描述符。
field_info結構格式如下:
field_info
{
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
access_flags項的值是用於定義字段被訪問權限和基礎屬性的掩碼標志。取值范圍如下表:
name_index項的值必須是對常量池的一個有效索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示一個有效的字段的非全限定名。
descriptor_index項的值必須是對常量池的一個有效索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示一個有效的字段的描述符。
attributes_count的項的值表示當前字段的附加屬性的數量。
attributes表的每一個成員的值必須是attribute結構,一個字段可以有任意個關聯屬性。
方法
所有方法(Method),包括實例初始化方法和類初始化方法在內,都由method_info結構所定義。在一個Class文件中,不會有兩個方法同時具有相同的方法名和描述符。
method_info結構格式如下:
method_info
{
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
access_flags項的值是用於定義當前方法的訪問權限和基本屬性的掩碼標志,取值范圍如下表:
標記名 值 說明
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 bridge,方法由編譯器產生
ACC_VARARGS 0x0080 表示方法帶有變長參數
ACC_NATIVE 0x0100 native,方法引用非java語言的本地方法
ACC_ABSTRACT 0x0400 abstract,方法沒有具體實現
ACC_STRICT 0x0800 strictfp,方法使用FP-strict浮點格式
ACC_SYNTHETIC 0x1000 方法在源文件中不出現,由編譯器產生
name_index項的值必須是對常量池的一個有效索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結構。
descriptor_index項的值必須是對常量池的一個有效索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示一個有效的方法的描述符。
attributes_count的項的值表示這個方法的附加屬性的數量。attributes表的每一個成員的值必須是attribute結構,一個方法可以有任意個與之相關的屬性。
屬性:
屬性(Attributes)在Class文件格式中的ClassFile結構、field_info 結構,method_info結構和Code_attribute結構都有使用,所有屬性的通用格式如下:
attribute_info
{
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
對於任意屬性,attribute_name_index必須是對當前Class文件的常量池的有效16位無符號索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示當前屬性的名字。attribute_length項的值給出了跟隨其后的字節的長度,這個長度不包括attribute_name_index和attribute_name_index項的6個字節。
對於字段、方法、和屬性的結構,我們很容易的可以通過javap工具查看到。
關於Class文件的驗證可能會放到后面Java虛擬機加載類的過程中學習。