一、前言
隨着我們學習的不斷深入,我相信讀者對class文件很感興趣,class文件是用戶編寫程序與虛擬機之前的橋梁,程序通過編譯形成class文件,class文件之后會載入虛擬機,被虛擬機執行,下面我么來一起揭開class文件的神秘面紗。
二、什么是class文件
class文件是二進制文件,通常是以.class文件結尾的文件,它是以8位字節為基礎單位的二進制流,各個數據項緊密排列在class文件中,數據項的基本類型為u1,u2,u4,u8,分別表示一個字節,兩個字節,四個字節,八個字節的無符號數。
三、class文件數據結構
其實對於class文件而言,總體的數據結構看上去很規整,具體的結構如下圖所示
下面我們將用一個例子詳細講解class文件的各個部分。
四、示例

public class Test implements Cloneable { private String name; public Test() { } public Test(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return name; } }
說明:以上是一個很簡單和通用的類,下面我們的分析都將基於這個類。
經過編譯后,得到class文件,使用WinHex打開,class文件內容如下
下面我們將從這個文件的內容入手,慢慢分析class文件各個部分。
五、class內容詳解
5.1 magic
class文件的最開始4個字節為magic(魔數),用來確定該class文件能夠被虛擬機接受。而在我們的class文件中,我們可以看到最開始4個字節是CAFEBABE。所有的class文件的開始4個字節都是CAFEBABE。
5.2 minor_version && major_version
主次版本號,會隨着Java技術的發展而變化,表示虛擬機能夠處理的版本號。在magic之后的minor_version和major_version分別是0和52(52 = 3 * 16 + 4)。
5.3 constant_pool_count && constant_pool
常量池中常量表的數量和常量表,常量池中的每一項是常量表,具體的常量表包含類和接口相關的常量,存了很多字面量和符號引用。字面量主要包括了文本字符串和final常量。符號引用包括:1. 類和接口的全限定名 2. 字段的名稱和描述符 3. 方法的名稱和描述符。
常量池中的項目包含如下類型:
從上面的圖中我們可以知道,常量池中常量項(常量項都對應一個表)為23(23 = 1 * 16 + 7),值得注意的是常量項的索引值從1開始,到22,總共22項,索引值為0的項預留出來,暫時還未使用。緊接着就是常量項,每個常量項的第一個字節u1表示標志(tag),標志(tag)表示是什么類型的項目,標志的值為上表給出的值,如標志為1(tag = 1),表示CONSTANT_Utf8_info項目。上圖中的第一個常量項為的標志tag的值為10(10 = 0 * 16 + A),為CONSTANT_Methodreef_info表,表示類中方法的符號引用。其中CONSTANT_Methodref_info表的結構如下
接着,在tag后面是u2類型的index項目,為4(4 = 0 * 16 + 4),表示指向常量池的第四項,由描述可知,第四項應該是CONSTANT_Class_info項,接着,又是u2類型的index項目,為18(18 = 1 * 16 + 2),表示指向常量池的第18項,由表的描述可知,第十八項應該是CONSTANT_NameAndType_info項,正確性我們之后進行驗證。第一個常量項CONSTANT_Methodref_info就完了。
緊接着第一個常量項是第二個常量項,tag為9(0 * 16 + 9),表示CONSTANT_Fieldref_info表,表示字段的符號引用。CONSTANT_Field_info的表結構如下
接着,在tag后面的是u2類型的index項目,為3(0 * 16 + 3),表示指向常量池的第三項,應該為CONSTANT_Class_info項,緊接着是index項目,為19(1 * 16 + 3),表示指向常量池的第19項,應該是CONSTANT_NameAndType_info項。
接着,是第三項常量,tag為7(0 * 16 + 7),表示CONSTANT_Class_info表,其中,其表結構如下
接着tag的為類型為u2的index,為20(1 * 16 + 4),表示指向常量池的第二十項,表示全限定名。
接着第四項常量,tag為7(0 * 16 + 7),表示CONSTANT_Class_info表,表結構已經介紹了,接着是u2的index,為21(1 * 16 + 5),表示指向全限定名。
接着第五項常量,tag為7,u2的類型的index為22(1 * 16 + 6),表示指向全限定名。
同理,按照這樣的方法進行分析,最后給出一個總的常量池表如下。
說明:#表示常量項的索引,Utf8表中存放的是具體的字符串。如#6中存放的就是字符串name,#10中存放的就是字符串Code,關於表示的具體含義,我們稍后會進行解釋。
除去我們之前介紹的常量表結構,常量池中其他常量表的結構分別如下:
說明:描述符分為字段描述符和方法描述符,字段描述符用來描述字段的數據類型,方法的描述符用來描述方法的參數列表(包括數量、類型、順序)和返回值。基本類型和對象的描述符如下:
說明:上表中並沒有指出出現數組了如何描述,每一個維度使用一個前置的"["來描述,如int[]描述為[I,String[]描述為[Ljava/lang/String;long類型是使用字符J進行標識,對象類型是使用L字符進行標識。如String類型描述為Ljava/lang/String;short類型描述為S,對於方法描述符而言,按照先參數列表,后返回值進行描述,參數列表按照參數順序放在小括號"()"內部,如void inc(int i)描述為(I)V;int getName()描述為()I;void setName(String name)描述為(Ljava/lang/String)V;方法的描述符與方法名稱是分開進行的,方法描述中並沒有包含方法名。
5.4 access_flags
常量池后的兩個字節,用於識別類或接口層次的訪問信息,如,這個class是類或者是接口,是否為public,abstract,final等等。具體的標志含義如下:
說明:其中ACC_INTERFACE與ACC_FINAL不能同時存在。
從之前的字節碼中可以知道,access_flags為0x0021(0x0021 = 0x0020|0x0001),即為public,並且允許使用invokespecial字節碼。
5.5 this_class
接着access_flags后面的u2類型的this_class,表示對常量池的索引,該索引項為CONSTANT_Class_info類型,從前面我們知道this_class為0x0003,表示對常量池第三項的索引,第三項我們知道確實是CONSTANT_Class_info類型,而第三項所表示的內容為Test,即表示當前類。
5.6 super_class
接着this_class后面的是u2類型的super_class,表示對常量池的索引,從前面我們知道super_class為0x0004,表示對常量池第四項的索引,第四項我們知道是CONSTANT_Class_info類型,而第四項所表示的內容為java/lang/Object,表示Test的父類為Object類。
5.7 interfaces_count && interfaces
接着super_class后面的u2類型表示接口數量,此接口數量為該類直接實現或者由接口所擴展接口的數量。從前面我們可以知道,interfaces_count為0x0001,表示接口數量為1,從程序中我們也可以知道確實是只實現了Cloneable接口。
接着就是類型為u2的interfaces,表示對常量池的索引,值為0x0005,表示對第五項的索引,第五項為CONSTANT_Class_info類型,所表示的內容為java/lang/Cloneable,從源程序我們可以進行驗證。
5.8 fields_count && fields
接着interfaces后面的是類型為u2的fields_count(包括類變量和實例變量,不包括局部變量),值為0x0001,為1,從源程序我們知道只聲明了一個實例變量name,所以為1。接着fields_count的是類型為fields_info表,field_info表的具體結構如下
接着fields_count后的是field_info表,首先是u2類型的access_flags,access_flags的具體含義如下表所示
說明:public、private、protected只能會有一個有效。final、volatile只能有一個有效。
我們可以知道access_flags為0x0002,表示為private,緊接着是類型為u2的name_index,值為0x0006,表示對常量池第六項的索引,常量池第六項為Class_Utf8_info類型,內容為name,則表示了字段的名稱。接着是類型為u2的descriptor_index,值為0x0007,表示對常量池第七項的索引,常量池第七項為Class_Utf8_info類型,內容為Ljava/lang/String,緊接着是類型為u2的attributes_count,為0x0000,表示field_info表沒有嵌套attribute_info表。
最后的field_info表結構如下:
5.9 methods_count && methods
fields后面的是類型為u2的methods_count,methods_count的計數只包括在該類或接口中顯示定義的方法,不包括從超類或父接口繼承來的方法,我們可以知道methods_count的值為0x0004,表示有四個方法,從源程序我們也可以進行驗證。緊接着methods_count的是method_info表,method_info表的具體結構如下(與field_info完全相同)
而對於access_flags標志種類如下
method_count為4表示接下來有4個method_info表。
首先是第一個method_info表,u2類型的access_flags,為0x0001,表示public,接着是類型為u2的name_index,為0x0008,表示對常量池第八項的索引,第八項為Class_Utf8_info類型,內容為<init>,表示實例初始化方法,由編譯器產生;接着是類型為u2的descriptor_index,為0x0009,表示對常量池第九項的索引,第九項為Class_Utf8_info類型,內容為()V,表示參數為空,返回值為void,接着是類型為u2的attributes_count,為0x0001,表示有一個屬性表;接着是attribute_info表,attribute_info表的結構如下:
接着attributes_count的是類型為u2的attribute_name_index,為0x000A,指向常量池第十項索引,第十項類型為Class_Utf8_info類型,內容為Code,Code屬性表示屬性的具體類別;接着是類型為u4的attribute_length,為0x00000021,表示屬性長度為33(2 * 16 + 1),接着就是具體每個屬性的info信息,對於Code屬性而言,其結構如下
接着attribute_length的是類型為u2的max_stack,為0x0001,表示操作數棧的最大深度,接着max_stack的是類型為u2的max_locals,為0x0001,表示局部變量所需的存儲空間大小為1,局部變量表的單位為slot,(byte、char、float、int、short、boolean等不超過32為的數據類型只占據一個slot,double、long64為數據類型需要兩個slot),局部變量表可以存放方法參數(實例方法的this引用)、顯式處理器的參數catch中所定義的異常、方法體中定義的局部變量。接着max_locals的是類型為u4的code_length,為0x00000005,為5,表示code代碼的長度為5,接着code的是類型為u2的exception_table_length,為0x0000,表示不存在異常表,接着是類型為u2的attributes_count(exception_table_length為0),為0x0001,為1,表示屬性數量為1,表示有一個屬性表,接着就是attribute_info表,類型為u2的attribute_name_index,為0x000B,表示對常量池第11項索引,第11項類型為Class_Utf8_info,內容為LineNumberTable,表示具體的屬性,LineNumberTable的具體結構如下圖所示
接着attribute_name_index的是類型為u4的attribute_length,為0x0000000A,長度為10,表示屬性長度為10,接着attribute_length的是類型為u2的line_number_table_length,為0x0002,為2,表示有兩個line_number_info表,line_number_info表的具體結構如下:
首先是第一個line_number_info表,類型為u2的start_pc,為0x0000,為0;接着是類型為u2的line_number,為0x0003,為3。第二個line_number_info表,類型為u2的start_pc,為0x0004,為4,接着是line_number,為0x0005,為5;
至此,第一個method_info表就已經分析完了,第一個method_info表的包含結構如下圖所示。
第二個、第三個、第四個Method_info都可以按照第一個Method_info表的方法進行類推。最后的4個表的說明如下
除了上面介紹的屬性表之外,還有其他的屬性表,下面進行介紹。
5.10 attributes_count && attributes
接在methods后面的是attributes_count,attributes_count為0x0001,表示有一個attribute_info表,接着attribute_count后面的是attribute_name_index,為0x0010,表示指向常量池第16項的索引,第16項類型為Class_Utf8_info類型,內容為SourceFile,表示屬性為SourceFile,SourceFile屬性的具體結構如下
可以看到attributes_count為0x0001,為1,表示有一個屬性表,緊接着,attribute_name_index為0x0010,為16,對應常量池第十六項,類型為Class_Utf8_info,內容為SourceFile,類型為u4的attribute_length,為0x00000002,值為2,緊接着是類型為u2的sourcefile_index,為0x0011,為17。
至此,整個class文件都已經解析完成了,其實經過分析,我們發現其實分析class文件並不困難,都有固定的格式。
六、特殊字符串
常量池容納的符號引用包括三種特殊的字符串:全限定名、簡單名稱、描述符。全限定名為類或接口的全限定名,如java.lang.Object對象的全限定名為java/lang/Object,用/代替.即可。簡單名稱為字段名或方法名的簡單名稱,如Object對象的toString()方法的簡單名稱為toString,描述符我們在之前已經介紹過了。
七、指令介紹
7.1 方法調用指令:
1. invokevirtual,用於調用對象的實例方法,根據對象的實際類型進行分派。
2. invokeinterface,用於調用接口方法,在運行時搜索一個實現了該接口方法的對象,找出合適的方法進行調用。
3. invokespecial,用於調用需要特殊處理的實例方法,包括實例初始化方法、私有方法、父類方法。
4. invokestatic,用於調用類方法,static方法。
5. invokedynamic,用於在運行時動態解析出調用點限定符所引用的方法,並執行該方法。
7.2 返回指令
ireturn(boolean、byte、char、short、int)、lreturn、freturn、dreturn、areturn(返回為對象引用類型)、return(返回為void)
7.3 同步指令
虛擬機支持方法級的同步和方法內部一段指令序列的同步,都使用管程(Monitor)來支持。方法級(synchronized修飾)同步時隱式的,無需通過字節碼指令來控制,方法調用時檢查ACC_SYNCHRONIZED標志。方法內部的synchronized語句塊使用monitorenter,monitorexit指令來確保同步。
七、總結
class文件看似很復雜,其實經過分析我們發現class文件並不難,通過分析class文件,我們知道了源程序經過編譯器編譯之后如何組織在class文件中,進而為虛擬機執行程序提供搭起了橋梁。也相信經過分析,讀者也能夠分析class文件了,那么我們的目的也就達到了,謝謝各位園友的觀看~