Java虛擬機和Class檔案是Java實作系統無關性的基石,
Class檔案是JVM實作語言無關性的基石,

Class檔案中包含了Java虛擬機指令集、符號表以及若干其他輔助資訊,
每一個 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];
}
簡單看一下各項的含義:

由于 Class 檔案結構沒有任何分隔符,所以無論是每個資料項的的順序還是數量,都是嚴格限定的,哪個位元組代表什么含義,長度多少,先后順序如何,都是不允許改變的,
接下來我們來具體學習每項的含義,
1、魔數
這是基本上每個Java開發人員的第一個Java程式:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
我使用的是Idea工具,運行,target目錄下會生成對應的class檔案,為了查看檔案的十六進制資訊,我們可以安裝一個插件HexView,

安裝完成之后,選擇class檔案,右鍵 HexView,打開后的十六進制如下:

第一行中有一串特殊的字符 cafebabe,它就是一個魔數,是 JVM 識別 class 檔案的標志,JVM 會在驗證階段檢查 class 檔案是否以該魔數開頭,如果不是則會拋出 ClassFormatError,
這段位元組很有意思——咖啡寶貝,Java原來不止是咖啡,還是寶貝😂
2、版本號
緊跟著魔數后面的四個位元組 0000 0031 存盤的是 class 檔案的版本號:第 5 和第 6 個位元組是次版本號(Minor Version),第 7 和第 8 個位元組是主版本號(Major Version),
Java的版本號是從 45 開始的,JDK1.1 之后的每個 JDK 大版本發布主版本號向上加1(JDK1.0JDK1.1使用了45.045.3的版本號),高版本的 JDK 能向下兼容以前版本的 Class 檔案,但不能運行以后版本的 Class 檔案,即使檔案格式未發生變化,
0000 0031 對應的十進制為49,是JDK8的內部版本號,
3、常量池
緊接著主、次版本號之后的是常量池入口,
由于常量池中常量的數量是不固定的,所以在常量池的入口需要放置一項u2型別的資料,代表常量池容量計數值(constant_pool_count),與Java中語言習慣不同,這個容量計數是從1而不是0開始的,

如圖所示,常量池容量為十六進制數0x0022,即十進制的34,這就代表常量池中有33項常量,索引值范圍為1~33,Class檔案結構中只有常量池的容量計數是從1開始,對于其他集合型別,包括介面索引集合、欄位表集合、方法表集合等的容量計數都與一般習慣相同,是從0開始,
常量池中主要存放兩大類常量:字面量(Literal)和符號參考(Symbolic References),字面量比較接近于Java語言層面的常量概念,如文本字串、被宣告為final的常量值等,而符號參考則屬于編譯原理方面的概念,主要包括下面幾類常量:
-
被模塊匯出或者開放的包(Package)
-
類和介面的全限定名(Fully Qualified Name)
-
欄位的名稱和描述符(Descriptor)
-
方法的名稱和描述符
-
方法句柄和方法型別(Method Handle、Method Type、Invoke Dynamic)
-
動態呼叫點和動態常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
這17類常量結構只有一個相同之處,表結構起始的第一位是個u1型別的標志位(tag),代表著當前常量屬于哪種常量型別,
17種常量型別所代表的具體含義如表所示:
| 型別 | 標志 | 描述 |
|---|---|---|
| CONSTANT_Utf8_info | 1 | UTF-8 編碼的字串 |
| CONSTANT_Integer_info | 3 | 整型字面量 |
| CONSTANT_Float_info | 4 | 浮點型字面量 |
| CONSTANT_Long_info | 5 | 長整型型字面量 |
| CONSTANT_Double_info | 6 | 雙精度浮點型字面量 |
| CONSTANT_Class_info | 7 | 類或介面的符號參考 |
| CONSTANT_String_info | 8 | 字串型別字面量 |
| CONSTANT_Fieldref_info | 9 | 欄位的符號參考 |
| CONSTANT_Methodref_info | 10 | 類中方法的符號參考 |
| CONSTANT_InterfaceMethodref_info | 11 | 介面中方法的符號參考 |
| CONSTANT_NameAndType_info | 12 | 欄位或方法的部分符號參考 |
| CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
| CONSTANT_MethodType_info | 16 | 表示方法型別 |
| CONSTANT_Dynamic_info | 17 | 表示一個動態計算常量 |
| CONSTANT_InvokeDynamic_info | 18 | 表示一個動態方法呼叫點 |
| CONSTANT_Moudle_info | 19 | 表示一個模塊 |
| CONSTANT_Package_info | 20 | 表示一個模塊中開放或者匯出的包 |
常量池非常繁瑣,17種常量型別各自有著完全獨立的資料結構,彼此之間沒有什么共性和聯系,
我們直接看一下常量池中的17種資料型別的結構總表:



4、訪問標志
在常量池結束之后,緊接著的2個位元組代表訪問標志(access_flags),這個標志用于識別一些類或者介面層次的訪問資訊,包括:這個Class是類還是介面;是否定義為public型別;是否定義為abstract型別;如果是類的話,是否被宣告為final等等,
具體的標志位以及標志的含義如表:
| 標志名稱 | 標志值 | 含義 |
|---|---|---|
| ACC_PUBLIC | 0x0001 | 是否為 Public 型別 |
| ACC_FINAL | 0x0010 | 是否被宣告為 final,只有類可以設定 |
| ACC_SUPER | 0x0020 | 是否允許使用 invokespecial 位元組碼指令的新語意 |
| ACC_INTERFACE | 0x0200 | 標志這是一個介面 |
| ACC_ABSTRACT | 0x0400 | 是否為 abstract 型別,對于介面或者抽象類來說,次標志值為真,其他型別為假 |
| ACC_SYNTHETIC | 0x1000 | 標志這個類并非由用戶代碼產生 |
| ACC_ANNOTATION | 0x2000 | 標志這是一個注解 |
| ACC_ENUM | 0x4000 | 標志這是一個列舉 |
access_flags中一共有16個標志位可以使用,當前只定義了其中9個,沒有使用到的標志位要求一 律為零,
5、類索引、父類索引與介面索引集合
這三者為什么放在一起呢?因為這三者用來確定類的繼承關系,
類索參考于確定這個類的全限定名,父類索參考于確定這個類的父類的全限定名,由于Java語言不允許多重繼承,所以父類索引只有一個,除了java.lang.Object之外,所有的Java類都有父類,因此除了 java.lang.Object外,所有Java類的父類索引都不為0,
介面索引集合就用來描述這個類實作了哪些介面,這些被實作的介面將按implements關鍵字(后的介面順序從左到右排列在介面索引集合中,
6、欄位表集合
介面索引結束后,接著是欄位表(field_info),它用于描述介面或者類中宣告的變數——這里的欄位(Field)只包括類級變數以及實體級變數,不包括在方法內部宣告的區域變數,
描述的主要資訊包括:
①、欄位的作用域(public,protected,private修飾)
②、是類級變數還是實體級變數(static修飾)
③、是否可變(final修飾)
④、并發可見性(volatile修飾,是否強制從主從讀寫)
⑤、是否可序列化(transient修飾)
⑥、欄位資料型別(8種基本資料型別,物件,陣列等參考型別)
⑦、欄位名稱
欄位表的結構如下:
| 型別 | 名稱 | 數量 |
|---|---|---|
| u2 | access_flags | 1 |
| u2 | name_index | 1 |
| u2 | descriptor_index | 1 |
| u2 | attributes_count | 1 |
| attribute_info | attributes | attributes_count |
access_flags是該欄位的的訪問標志,它和類中的訪問標志很類似,用以描述該欄位的權限型別:private、protected、public;并發可見性:volatile;可變性:final;
訪問標志詳情如下圖所示:

由于Java語法規則的約束,ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED三個標志最多只能選擇其一,ACC_FINAL、ACC_VOLATILE不能同時選擇,介面之中的欄位必須有ACC_PUBLIC、ACC_STATIC、ACC_FINAL標志,
7、方法表集合
方法表的結構如同欄位表一樣,依次包括訪問標志(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表集合(attributes)幾項,如表所示:

有區別的部分只有方法訪問標志access_flag, 因為volatile關鍵字和transient關鍵字不能修飾方法,
方法表標志位及其取值如下:

8、屬性表集合
接下來終于到了最后一項:屬性表集合,
前面提到的Class檔案、欄位表、方法表都可以攜帶自己的屬性表集合,就是參考的這里,
屬性表集合中的屬性如下所示:


與Class檔案中其他的資料專案要求嚴格的順序、長度和內容不同,屬性表集合的限制寬松一些,不再要求各個屬性表具有嚴格順序,并且《Java虛擬機規范》允許只要不與已有屬性名重復,任何人實作的編譯器都可以向屬性表中寫入自己定義的屬性資訊,Java虛擬機運行時會忽略掉它不認識的屬性,
參考:
【1】:《深入理解Java虛擬機:JVM高級特性與最佳實踐》第三版
【2】:《Java虛擬機規范(Java_SE_7)》
【3】:jvms/se8
【4】:Java虛擬機詳解(九)------類檔案結構
【5】:一把小刀,直插 class 檔案的小心臟
【6】:JVM系列(二)-位元組碼檔案結構(基礎篇)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/279243.html
標籤:java
上一篇:Filter詳解
