小介:去年在讀《深入解析JVM》的時候寫的,記得當時還想著用自己的代碼解析字節碼的,最后只完成了一部分?,F在都不知道還有沒有保留著,貌似Apache有現成的BCEL工程可以做這件事。當時也只是為了學習。這份資料主要參考《深入解析JVM》和《Java虛擬機規范》貌似是1.2版本的,整理出來的。里面包含了一些自己的理解和用實際代碼的測試。有興趣的童鞋可以研究研究。嘿嘿。要有錯誤也希望能為小弟指點出來,感激不盡。:)
1.總體格式
Class File format |
type | descriptor | remark |
u4 | magic | 0xCAFEBABE |
u2 | minor_version | |
u2 | major_version | |
u2 | constant_pool_count | |
cp_info | constant_pool[cosntant_pool_count – 1] | index 0 is invalid |
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] | |
2. 格式詳解
2.1 magic
magic被稱為“魔數”,用來標識.class文件的開頭。所有合法的.class字節碼都應該是該數開頭,占4個字節。
2.2 major_version.minor_version
major_version.minor_version合在一起形成當前.class文件的版本號,該版本號一般由編譯器產生,并且由sun定義。如59.0。它們一起占4個字節。
2.3 constant_pool
在Java字節碼中,有一個常量池,用來存放不同類型的常量。由于Java設計的目的之一就是字節碼需要經網絡傳輸的,因而字節碼需要比較緊湊,以減少網絡傳輸的流量和時間。常量池的存在則可以讓一些相同類型的值通過索引的方式從常量池中找到,而不是在不同地方有不同拷貝,縮減了字節碼的大小。
每個常量池中的項是通過cp_info的類型來表示的,它的格式如下:
cp_info format |
type | descriptor | remark |
u1 | tag | |
u1 | info[] | |
這里tag用來表示當前常量池不同類型的項。info中存放常量池項中存放的數據。
tag中表示的數據類型:
CONSTANT_Class_info (7)、
CONSTANT_Integer_info (3)、
CONSTANT_Long_info (5)、
CONSTANT_Float_info (4)、
CONSTANT_Double_info (6)、
CONSTANT_String_info (8)、
CONSTANT_Fieldref_info (9)、
CONSTANT_Methodref_info (10)、
CONSTANT_InterfaceMethodref_info (11)、
CONSTANT_NameAndType_info (12)、
CONSTANT_Utf8_info (1)、
注:在Java字節碼中,所有boolean、byte、char、short類型都是用int類型存放,因而在常量池中沒有和它們對應的項。
2.3.1 CONSTANT_Class_info
用于記錄類或接口名(used to represent a class or an interface)
CONSTANT_Class_info format |
type | descriptor | remark |
u1 | tag | CONSTANT_Class (7) |
u2 | name_index | constant_pool中的索引,CONSTANT_Utf8_info類型。表示類或接口名。 |
注:在Java字節碼中,類和接口名不同于源碼中的名字,詳見附件A.
2.3.2 CONSTANT_Integer_info
用于記錄int類型的常量值(represent 4-byte numeric (int) constants:)
CONSTANT_Integer_info |
type | descriptor | remark |
u1 | tag | CONSTANT_Integer (3) |
u4 | bytes | 整型常量值 |
2.3.3 CONSTANT_Long_info
用于記錄long類型的常量值(represent 8-byte numeric (long) constants:)
CONSTANT_Long_info |
type | descriptor | remark |
u1 | tag | CONSTANT_Long (5) |
u4 | high_bytes | 長整型的高四位值 |
u4 | low_bytes | 長整型的低四位值 |
2.3.4 CONSTANT_Float_info
用于記錄float類型的常量值(represent 4-byte numeric (float) constants:)
CONSTANT_Float_info |
type | descriptor | remark |
u1 | tag | CONSTANT_Float(4) |
u4 | bytes | 單精度浮點型常量值 |
幾個特殊值:0x7f800000 => Float.POSITIVE_INFINITY、0xff800000 => Float.NEGATIVE_INFINITY、
0x7f800001 to 0x7fffffff => Float.NaN、0xff800001 to 0xffffffff => Float.NaN
2.3.5 CONSTANT_Double_info
用于記錄double類型的常量值(represent 8-byte numeric (double) constants:)
CONSTANT_Double_info |
type | descriptor | remark |
u1 | tag | CONSTANT_Double(6) |
u4 | high_bytes | 雙精度浮點的高四位值 |
u4 | low_bytes | 雙精度浮點的低四位值 |
幾個特殊值:0x7ff0000000000000L => Double.POSITIVE_INFINITY、
0xfff0000000000000L => Double.NEGATIVE_INFINITY
0x7ff0000000000001L to 0x7fffffffffffffffL => Double.NaN 、
0xfff0000000000001L to 0xffffffffffffffffL => Double.NaN
2.3.6 CONSTANT_String_info
用于記錄常量字符串的值(represent constant objects of the type String:)
CONSTANT_String_info |
type | descriptor | remark |
u1 | tag | CONSTANT_String(8) |
u2 | string_index | constant_pool中的索引,CONSTANT_Utf8_info類型。表示String類型值。 |
2.3.7 CONSTANT_Fieldref_info
用于記錄字段信息(包括類或接口中定義的字段以及代碼中使用到的字段)。
CONSTANT_Fieldref_info |
type | descriptor | remark |
u1 | tag | CONSTANT_Fieldref(9) |
u2 | class_index | constant_pool中的索引,CONSTANT_Class_info類型。記錄定義該字段的類或接口。 |
u2 | name_and_type_index | constant_pool中的索引,CONSTANT_NameAndType_info類型。指定類或接口中的字段名(name)和字段描述符(descriptor)。 |
2.3.8 CONSTANT_Methodref_info
用于記錄方法信息(包括類中定義的方法以及代碼中使用到的方法)。
CONSTANT_Methodref_info |
type | descriptor | remark |
u1 | tag | CONSTANT_Methodref(10) |
u2 | class_index | constant_pool中的索引,CONSTANT_Class_info類型。記錄定義該方法的類。 |
u2 | name_and_type_index | constant_pool中的索引,CONSTANT_NameAndType_info類型。指定類中扽方法名(name)和方法描述符(descriptor)。 |
2.3.9 CONSTANT_InterfaceMethodref_info
用于記錄接口中的方法信息(包括接口中定義的方法以及代碼中使用到的方法)。
CONSTANT_InterfaceMethodref_info |
type | descriptor | remark |
u1 | tag | CONSTANT_InterfaceMethodref(11) |
u2 | class_index | constant_pool中的索引,CONSTANT_Class_info類型。記錄定義該方法的接口。 |
u2 | name_and_type_index | constant_pool中的索引,CONSTANT_NameAndType_info類型。指定接口中的方法名(name)和方法描述符(descriptor)。 |
2.3.10 CONSTANT_NameAndType_info
記錄方法或字段的名稱(name)和描述符(descriptor)(represent a field or method, without indicating which class or interface type it belongs to:)。
CONSTANT_NameAndType_info |
type | descriptor | remark |
u1 | tag | CONSTANT_NameAndType (12) |
u2 | name_index | constant_pool中的索引,CONSTANT_Utf8_info類型。指定字段或方法的名稱。 |
u2 | descriptor_index | constant_pool中的索引,CONSTANT_utf8_info類型。指定字段或方法的描述符(見附錄C) |
2.3.11 CONSTANT_Utf8_info
記錄字符串的值(represent constant string values. String content is encoded in modified UTF-8.)
modifie
d UTF-8 refer to :
http://download.ora
cle.com/javase/1.4.2/docs/api/java/io/DataInputStream.html
CONSTANT_Utf8_info |
type | descriptor | remark |
u1 | tag | CONSTANT_Utf8 (1) |
u2 | length | bytes所代表 的字符串的長度 |
u1 | bytes[length] | 字符串的byte數據,可以通過DataInputStream中的readUtf()方法(實例方法或靜態方法讀取該二進制的字符串的值。) |
2.4 access_flags
指定類或接口的訪問權限。
類或接口的訪問權限 |
Flag Name | Value | Remarks |
ACC_PUBLIC | 0x0001 | pubilc,包外可訪問。 |
ACC_FINAL | 0x0010 | final,不能有子類。 |
ACC_SUPER | 0x0020 | 用于兼容早期編譯器,新編譯器都設置該標記,以在使用 invokespecial指令時對子類方法做特定處理。 |
ACC_INTERFACE | 0x0200 | 接口,同時需要設置:ACC_ABSTRACT。不可同時設置:ACC_FINAL、ACC_SUPER、ACC_ENUM |
ACC_ABSTRACT | 0x0400 | 抽象類,無法實例化。不可和ACC_FINAL同時設置。 |
ACC_SYNTHETIC | 0x1000 | synthetic,由編譯器產生,不存在于源代碼中。 |
ACC_ANNOTATION | 0x2000 | 注解類型(annotation),需同時設置:ACC_INTERFACE、ACC_ABSTRACT |
ACC_ENUM | 0x4000 | 枚舉類型 |
2.5 this_class
this_class是指向constant pool的索引值,該值必須是CONSTANT_Class_info類型,指定當前字節碼定義的類或接口。
2.6 super_class
super_class是指向constant pool的索引值,該值必須是CONSTANT_Class_info類型,指定當前字節碼定義的類或接口的直接父類。只有Object類才沒有直接父類,此時該索引值為0。并且父類不能是final類型。接口的父類都是Object類。
2.7 interfaces
interfaces數組記錄所有當前類或接口直接實現的接口。interfaces數組中的每項值都是一個指向constant pool的索引值,這些值必須是CONSTANT_Class_info類型。數組中接口的順序和源代碼中接口定義的順序相同。
2.8 fields
fields數組記錄了類或接口中的所有字段,包括實例字段和靜態字段,但不包含父類或父接口中定義的字段。fields數組中每項都是field_info類型值,它描述了字段的詳細信息,如名稱、描述符、字段中的attribute等。
field_info |
type | descriptor | remark |
u2 | access_flags | 記錄字段的訪問權限。見2.8.1 |
u2 | name_index | constant_pool中的索引,CONSTANT_Utf8_info類型。指定字段的名稱。 |
u2 | descriptor_index | constant_pool中的索引,CONSTANT_Utf8_info類型,指定字段的描述符(見附錄C)。 |
u2 | attributes_count | attributes包含的項目數。 |
attribute_info | attributes[attributes_count] | 字段中包含的Attribute集合。見2.8.2-2.8.7 |
注:fields中的項目和CONSTANT_Fieldref_info中的項目部分信息是相同的,他們主要的區別是CONSTANT_Fieldref_info中的項目不僅包含了類或接口中定義的字段,還包括在字節碼中使用到的字段信息。不過這里很奇怪,為什么field_info結構中不把name_index和descriptor_index合并成fieldref_index,這樣的class文件不是更加緊湊嗎??不知道這是sun因為某些原因故意這樣設計還是這是他們的失誤??
2.8.1 字段訪問權限
字段的訪問權限 |
Flag Name | Value | Remarks |
ACC_PUBLIC | 0x0001 | pubilc,包外可訪問。 |
ACC_PRIVATE | 0x0002 | private,只可在類內訪問。 |
ACC_PROTECTED | 0x0004 | protected,類內和子類中可訪問。 |
ACC_STATIC | 0x0008 | static,靜態。 |
ACC_FINAL | 0x0010 | final,常量。 |
ACC_VOILATIE | 0x0040 | volatile,直接讀寫內存,不可被緩存。不可和ACC_FINAL一起使用。 |
ACC_TRANSIENT | 0x0080 | transient,在序列化中被忽略的字段。 |
ACC_SYNTHETIC | 0x1000 | synthetic,由編譯器產生,不存在于源代碼中。 |
ACC_ENUM | 0x4000 | enum,枚舉類型字段 |
注:接口中的字段必須同時設置:ACC_PUBLIC、ACC_STATIC、ACC_FINAL
2.8.2 ConstantValue Attribute (JVM識別)
ConstantValue Attribute |
type | descriptor | remark |
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info類型。指定Attribute的名稱(“ConstantValue”)。 |
u4 | attribute_length | 該Attribute內容的字節長度(固定值:2) |
u2 | constant_value_index | constant_pool中的索引, CONSTANT_Integer_info(int,boolean,char、short、byte)、 CONSTANT_Float_info(float)、 Constant_Double_info(double)、 CONSTANT_Long_info(long) CONSTANT_String_info(String)類型 |
每個常量字段(final,靜態常量或實例常量)都包含有且僅有一個ConstantValue Attribute。ConstantValue Attribute結構用于存儲一個字段的常量值。
對一個靜態常量字段,該常量值會在類或接口被初始化之前,由JVM負責賦給他們,即它在任何靜態字段之前被賦值。
對一個非靜態常量字段,該值會被虛擬機忽略,它的賦值由生成的實例初始化函數(<init>)實現。如類:
class A {
public static final int fa = 10;
public final int fa2 = 30;
private static int sa = 20;
static {
sa = 30;
}
}
生成的字節碼如下:
// Compiled from Test.java (version 1.6 : 50.0, super bit)
class org.levin.insidejvm.miscs.staticinit.A {
public static final int fa = 10;
public final int fa2 = 30;
private static int sa;
static {};
0 bipush 20
2 putstatic org.levin.insidejvm.miscs.staticinit.A.sa : int [16]
5 bipush 30
7 putstatic org.levin.insidejvm.miscs.staticinit.A.sa : int [16]
10 return
public A();
0 aload_0 [this]
1 invokespecial java.lang.Object() [21]
4 aload_0 [this]
5 bipush 30
7 putfield org.levin.insidejvm.miscs.staticinit.A.fa2 : int [23]
10 return
2.8.3 Synthetic Attribute
參考2.11.1
2.8.4 Signature Attribute
參考2.11.2
2.8.5 Deprecated Attribute
參考2.11.3
2.8.6 RuntimeVisibleAnnotations Attribute
參考2.11.4
2.8.7 RuntimeInvisibleAnnotations Attribute
參考2.11.5
于2010-12-19