【Java虛擬機】JVM系列學習之Class文件詳細講解

一、前言

  隨著我們學習的不斷深入,我相信讀者對class文件很感興趣,class文件是用戶編寫程序與虛擬機之前的橋樑,程序通過編譯形成class文件,class文件之後會載入虛擬機,被虛擬機執行,下面我們來一起揭開class文件的神秘面紗。

二、什麼是class文件

  class文件是二進制文件,通常是以.class文件結尾的文件,它是以8位字節為基礎單位的二進制流,各個數據項緊密排列在class文件中,數據項的基本類型為u1,u2,u4,u8,分別表示一個字節,兩個字節,四個字節,八個字節的無符號數。

【Java虛擬機】JVM系列學習之Class文件詳細講解

三、class文件數據結構

  其實對於class文件而言,總體的數據結構看上去很規整,具體的結構如下圖所示

【Java虛擬機】JVM系列學習之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文件內容如下

【Java虛擬機】JVM系列學習之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. 方法的名稱和描述符。

  常量池中的項目包含如下類型:

【Java虛擬機】JVM系列學習之Class文件詳細講解

  從上面的圖中我們可以知道,常量池中常量項(常量項都對應一個表)為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表的結構如下

【Java虛擬機】JVM系列學習之Class文件詳細講解

  接著,在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的表結構如下

【Java虛擬機】JVM系列學習之Class文件詳細講解

  接著,在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表,其中,其表結構如下

  

【Java虛擬機】JVM系列學習之Class文件詳細講解

  接著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),表示指向全限定名。

  同理,按照這樣的方法進行分析,最後給出一個總的常量池表如下。

【Java虛擬機】JVM系列學習之Class文件詳細講解


  說明:#表示常量項的索引,Utf8表中存放的是具體的字符串。如#6中存放的就是字符串name,#10中存放的就是字符串Code,關於表示的具體含義,我們稍後會進行解釋。

  除去我們之前介紹的常量表結構,常量池中其他常量表的結構分別如下:

  

【Java虛擬機】JVM系列學習之Class文件詳細講解

【Java虛擬機】JVM系列學習之Class文件詳細講解

【Java虛擬機】JVM系列學習之Class文件詳細講解

  

【Java虛擬機】JVM系列學習之Class文件詳細講解

【Java虛擬機】JVM系列學習之Class文件詳細講解

  說明:描述符分為字段描述符和方法描述符,字段描述符用來描述字段的數據類型,方法的描述符用來描述方法的參數列表(包括數量、類型、順序)和返回值。基本類型和對象的描述符如下:

【Java虛擬機】JVM系列學習之Class文件詳細講解

  說明:上表中並沒有指出出現數組瞭如何描述,每一個維度使用一個前置的"["來描述,如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等等。具體的標誌含義如下:

  

【Java虛擬機】JVM系列學習之Class文件詳細講解

  說明:其中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表的具體結構如下

【Java虛擬機】JVM系列學習之Class文件詳細講解

  接著fields_count後的是field_info表,首先是u2類型的access_flags,access_flags的具體含義如下表所示

【Java虛擬機】JVM系列學習之Class文件詳細講解

  說明: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表結構如下:

【Java虛擬機】JVM系列學習之Class文件詳細講解

  5.9 methods_count && methods

  fields後面的是類型為u2的methods_count,methods_count的計數只包括在該類或接口中顯示定義的方法,不包括從超類或父接口繼承來的方法,我們可以知道methods_count的值為0x0004,表示有四個方法,從源程序我們也可以進行驗證。緊接著methods_count的是method_info表,method_info表的具體結構如下(與field_info完全相同)

【Java虛擬機】JVM系列學習之Class文件詳細講解

  而對於access_flags標誌種類如下

【Java虛擬機】JVM系列學習之Class文件詳細講解

  method_count為4表示接下來有4個method_info表。

  首先是第一個method_info表,u2類型的access_flags,為0x0001,表示public,接著是類型為u2的name_index,為0x0008,表示對常量池第八項的索引,第八項為Class_Utf8_info類型,內容為,表示實例初始化方法,由編譯器產生;接著是類型為u2的descriptor_index,為0x0009,表示對常量池第九項的索引,第九項為Class_Utf8_info類型,內容為()V,表示參數為空,返回值為void,接著是類型為u2的attributes_count,為0x0001,表示有一個屬性表;接著是attribute_info表,attribute_info表的結構如下:

【Java虛擬機】JVM系列學習之Class文件詳細講解

  接著attributes_count的是類型為u2的attribute_name_index,為0x000A,指向常量池第十項索引,第十項類型為Class_Utf8_info類型,內容為Code,Code屬性表示屬性的具體類別;接著是類型為u4的attribute_length,為0x00000021,表示屬性長度為33(2 * 16 + 1),接著就是具體每個屬性的info信息,對於Code屬性而言,其結構如下

【Java虛擬機】JVM系列學習之Class文件詳細講解

  接著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的具體結構如下圖所示

【Java虛擬機】JVM系列學習之Class文件詳細講解

  接著attribute_name_index的是類型為u4的attribute_length,為0x0000000A,長度為10,表示屬性長度為10,接著attribute_length的是類型為u2的line_number_table_length,為0x0002,為2,表示有兩個line_number_info表,line_number_info表的具體結構如下:

【Java虛擬機】JVM系列學習之Class文件詳細講解

  首先是第一個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表的包含結構如下圖所示。

  

【Java虛擬機】JVM系列學習之Class文件詳細講解

  第二個、第三個、第四個Method_info都可以按照第一個Method_info表的方法進行類推。最後的4個表的說明如下

【Java虛擬機】JVM系列學習之Class文件詳細講解

  

【Java虛擬機】JVM系列學習之Class文件詳細講解

  

【Java虛擬機】JVM系列學習之Class文件詳細講解

【Java虛擬機】JVM系列學習之Class文件詳細講解

  除了上面介紹的屬性表之外,還有其他的屬性表,下面進行介紹。

  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屬性的具體結構如下

【Java虛擬機】JVM系列學習之Class文件詳細講解

  可以看到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文件了,那麼我們的目的也就達到了,謝謝各位園友的觀看~


分享到:


相關文章: