《Java虛擬機規范》閱讀(三):Class文件格式


  每一個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虛擬機加載類的過程中學習。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM