JVM類檔案結構
類檔案即.class檔案,能夠在java虛擬機器上執行。Java虛擬機器不和包括Java在內的任何語言繫結,只與Class檔案這種特定的二進位制檔案格式關聯,Class檔案中包含了Java虛擬機器指令集和符號表以及若干其他輔助資訊。class檔案可以由其他語言編譯而來,例如:JRuby、Jython、Scala等。
Class類檔案的結構
Class檔案是一組以8位位元組為基礎單位的二進位制流,各個資料專案嚴格按照順序緊湊地排列在Class檔案之中,中間沒有新增任何分隔符,這使得整個Class檔案中儲存的內容幾乎全部是程式執行的必要資料,沒有空隙存在。當遇到需要佔用8位位元組以上空間的資料項時,則會按照高位在前的方式分割成若干個8位位元組進行儲存。
Class檔案格式採用一種類似於C語言結構體的偽結構來儲存資料,這種偽結構只有兩種資料型別:無符號和表。無符號
整個Class檔案本質上就是一張表,構成這張表的資料項如下:
序號 | 型別 | 名稱 | 數量 | 備註 |
---|---|---|---|---|
1 | u4 | magic | 1 | 魔數,確定檔案是否是能被虛擬機器接受的Class檔案 |
2 | u2 | minor_version | 1 | 次版本號 |
3 | u2 | major_version | 1 | 主版本號 |
4 | u2 | constant_pool_count | 1 | 常量池容量計數值 |
5 | cp_info | constant_pool | constant_pool_count-1 | 常量池 |
6 | u2 | access_flags | 1 | 訪問標誌 |
7 | u2 | this_class | 1 | 類索引 |
8 | u2 | super_class | 1 | 父類索引 |
9 | u2 | interfaces_count | 1 | 介面計數值 |
10 | u2 | interfaces | interfaces_count | 介面索引集合 |
11 | u2 | fields_count | 1 | 欄位計數值 |
12 | field_info | fields | fields_count | 欄位表集合 |
13 | u2 | methods_count | 1 | 方法計數值 |
14 | method_info | methods | methods_count | 方法表集合 |
15 | u2 | attributes_count | 1 | 屬性計數值 |
16 | attribute_info | attributes | attributes_count | 屬性表集合 |
注意:Class檔案沒有任何分隔符,所以上表中的資料項,無論是順序還是資料量,甚至是資料儲存的位元組序,都是嚴格限定的,哪個位元組代表什麼含義,長度是多少,先後順序如何,都不允許改變。
常量池
常量池的入口在主次版本號之後,可以把常量池理解為Class檔案中的資源倉庫,是Class檔案結構中與其他專案關聯最多的資料型別,也是佔用Class檔案空間最大的資料專案之一,同時還是在Class檔案中第一個出現的表型別資料專案。常量池中的常量數量是不固定的,所以在常量池的入口需要放置一項u2型別的資料,代表常量池容量計數值,即:constant_pool_count。該容量計數器是從1而不是0開始的,將第0項常量空出來的目的在於滿足後面某些指向常量池的索引值的資料在特定情況下需要表達“不引用任何一個常量池專案”的含義,這種情況下就可以把索引值置為0來表示。
常量池中主要存放兩大類常量:字面量和符號引用。字面量,如文字字串、宣告為final的常量值等,符號引用包括三類常量:類和介面的全限定名、欄位的名稱和描述符、方法的名稱和描述符。當虛擬機器執行時,需要從常量池中獲得對應的符號引用,再在類建立的時候或者執行時解析、翻譯到具體的記憶體地址之中。
常量池中的常量專案結構表如下:
常量 | 專案 | 型別 | 描述 |
---|---|---|---|
CONSTANT_Utf8_info (UTF-8編碼的字串) |
tag | u1 | 值為1 |
length | u2 | UTF-8編碼的字串佔用的位元組數 | |
bytes | u1 | 長度為length的UTF-8編碼的字串 | |
CONSTANT_Integer_info (整型字面量) |
tag | u1 | 值為3 |
bytes | u4 | 按照高位在前儲存的int值 | |
CONSTANT_Float_info (浮點型字面量) |
tag | u1 | 值為4 |
bytes | u4 | 按照高位在前儲存的float值 | |
CONSTANT_Long_info (長整型字面量) |
tag | u1 | 值為5 |
bytes | u8 | 按照高位在前儲存的long值 | |
CONSTANT_Double_info (雙精度浮點型字面量) |
tag | u1 | 值為6 |
bytes | u8 | 按照高位在前儲存的double值 | |
CONSTANT_Class_info (類或介面的符號引用) |
tag | u1 | 值為7 |
index | u2 | 指向全限定名常量項的索引 | |
CONSTANT_String_info (字串型別字面量) |
tag | u1 | 值為8 |
index | u2 | 指向字串字面量的索引 | |
CONSTANT_Fieldref_info (欄位的符號引用) |
tag | u1 | 值為9 |
index | u2 | 指向宣告欄位的類或者介面描述符 CONSTANT_Class_info的索引項 |
|
index | u2 | 指向欄位描述符 CONSTANT_NameAndType的索引項 |
|
CONSTANT_Methodref_info (類中方法的符號引用) |
tag | u1 | 值為10 |
index | u2 | 指向宣告欄位的類或者介面描述符 CONSTANT_Class_info的索引項 |
|
index | u2 | 指向欄位描述符 CONSTANT_NameAndType的索引項 |
|
CONSTANT_InterfaceMethodref_info (介面中方法的符號引用) |
tag | u1 | 值為11 |
index | u2 | 指向宣告欄位的類或者介面描述符 CONSTANT_Class_info的索引項 |
|
index | u2 | 指向欄位描述符 CONSTANT_NameAndType的索引項 |
|
CONSTANT_NameAndType_info (欄位或方法的部分符號引用) |
tag | u1 | 值為12 |
index | u2 | 指向該欄位或方法名稱常量項的索引 | |
index | u2 | 指向該欄位或方法描述符常量項的索引 | |
CONSTANT_MethodHandle_info (表示方法控制代碼) |
tag | u1 | 值為15 |
reference_kind | u1 | 值必須在1~9之間(包括1和9), 它決定了方法控制代碼的型別。 方法控制代碼型別的值表示方法控制代碼的位元組碼行為 |
|
reference_index | u2 | 值必須是對常量池的有效索引 | |
CONSTANT_MethodType_info (標識方法型別) |
tag | u1 | 值為16 |
descriptor_index | u2 | 值必須是對常量池的有效索引, 常量池在該索引處的項必須是 CONSTANT_Utf8_info結構,表示方法的描述符 |
|
CONSTANT_InvokeDynamic_info (表示一個動態方法呼叫點) |
tag | u1 | 值為18 |
bootstrap_method_attr_index | u2 | 值必須是對當前Class檔案中引導方法表的 bootstrap_methods[]陣列的有效索引 |
|
name_and_type_index | u2 | 值必須是對當前常量池的有效索引, 常量池在該索引處的項必須是 CONSTANT_NameAndType_info結構, 表示方法名和方法描述符 |
訪問標誌
緊接著常量池後的兩個位元組是訪問標誌,訪問標誌用於識別一些類或者介面層次的訪問資訊,包括:這個Class是類還是介面;是否定義位public型別;是否定義位abstract型別;如果是類的話,是否被宣告位final等。
具體標誌如下:
- ACC_PUBLIC:是否位public型別
- ACC_FINAL:是否被宣告為final,只有類可設定
- ACC_SUPER:是否允許使用invokespecial位元組碼指令的新語意,invokespecial指令的意義在JDK1.0.2發生過改變,為了區別這條指令使用哪種語意,JDK1.0.2之後編譯出來的類的這個標誌都必須為真
- ACC_INTERFACE:標識這是一個介面
- ACC_ABSTRACT:是否為abstract型別,對於介面或者抽象類來說,此標誌值為真,其他類值為假
- ACC_SYNTHETIC:標識這個類並非由使用者程式碼產生的
- ACC_ANNOTATION:標識這是一個註解
- ACC_ENUM:標識這是一個列舉
訪問標誌中一共有16個標誌位可以使用,當前值定義了其中8個,沒有使用到的標誌位要求一律為0。
欄位表集合
欄位表用於描述介面或類中宣告的變數。欄位包括類級別變數以及例項級別變數,但不包括在方法內部宣告的區域性變數。欄位集合可以包括的資訊有:
- 欄位的作用域(private、public、protected)
- 欄位是例項變數還是類變數(static)
- 可變性(final)
- 併發可見性(volatile)
- 是否可被序列化(transient)
- 欄位資料型別
- 欄位名稱
這些修飾符都是布林值,要麼有某個修飾符,要麼沒有。欄位集合表訪問標誌如下:
- ACC_PUBLIC:欄位是否是public
- ACC_PRIVATE:欄位是否是private
- ACC_PROTECTED:欄位是否是protected
- ACC_STATIC:欄位是否是static
- ACC_FINAL:欄位是否是final
- ACC_VOLATILE:欄位是否是volatile
- ACC_TRANSIENT:欄位是否是transient
- ACC_SYNTHETIC:欄位是否由編譯器自動產生的
- ACC_ENUM:欄位是否是enum
欄位表集合中不會列出從超類或者父介面中繼承而來的欄位,但有可能列出原本Java程式碼中不存在的欄位,譬如在內部類中為了保持對外部類的訪問性,會自動新增指向外部類例項的欄位。另外,Java語言中欄位是無法過載的,兩個欄位的資料型別、修飾符不管是否相同,都必須使用不一樣的名稱,但是對於位元組碼而言,如果兩個欄位的描述符不一致,那欄位重名就是合法的。
方法表集合
方法表的結構和欄位表的結構一樣,一次包括訪問標誌、名稱索引、描述符索引、屬性表集合。訪問標誌如下:
- ACC_PUBLIC:方法是否是public
- ACC_PRIVATE:方法是否是private
- ACC_PROTECTED:方法是否是protected
- ACC_STATIC:方法是否是static
- ACC_FINAL:方法是否是final
- ACC_SYNCHRONIZED:方法是否為synchronized
- ACC_BRIDGE:方法是否由編譯器產生的橋接方法
- ACC_VARARGS:方法是否接受不定引數
- ACC_NATIVE:方法是否為native
- ACC_ABSTRACT:方法是否為abstract
- ACC_STRICTFP:方法是否為strictfp
- ACC_SYNTHETIC:方法是否為編譯器自動產生的
屬性表集合
屬性表集合不在要求各個屬性表具有嚴格順序,並且只要不與已有屬性名重複,任何人實現的編譯器都可以從屬性表中寫入自己定義的屬性資訊,Java虛擬機器執行時會忽略不認識的屬性。
一些屬性中的關鍵常用內容:
屬性名稱 | 使用位置 | 含義 |
---|---|---|
Code | 方法表 | Java程式碼編譯成的位元組碼指令 |
ConstantValue | 欄位表 | final關鍵字定義的常量值 |
Deprecated | 類、方法、欄位表 | 被宣告為deprecated的方法和欄位 |
Exceptions | 方法表 | 方法丟擲的異常 |
EnclosingMethod | 類檔案 | 僅當一個類為區域性類或者匿名類時才能擁有這個屬性, 這個屬性用於標識這個類所在的外圍方法 |
InnerClasses | 類檔案 | 內部類列表 |
LineNumberTable | Code屬性 | Java原始碼的行號與位元組碼指令的對應關係 |
LocalVariableTable | Code屬性 | 方法的區域性變數描述 |
StackMapTable | Code屬性 | JDK1.6中新增的屬性, 供新的型別檢查驗證器檢查和處理目標方法的區域性變數 和運算元棧所需要的型別是否匹配 |
Signature | 類、方法表、欄位表 | JDK1.5中新增的屬性, 這個屬性用於支援泛型情況下的方法簽名, 在Java語言中,任何類、介面、初始化方法或 者成員的泛型簽名如果包含了型別變數 或引數化型別,則Signature屬性會為它 記錄泛型簽名信息。由於Java的泛型採 用擦除法實現,在為了避免型別資訊被 擦除後導致簽名混亂,需要這個 屬性記錄泛型中的相關資訊 |
SourceFile | 類檔案 | 記錄原始檔名稱 |
SourceDebugExtension | 類檔案 | JDK1.6中新增的屬性,SourceDebugExtension屬性 用於儲存額外的除錯資訊。譬如在進行 JSP檔案調式時,無法通過Java堆疊來定位 到JSP檔案的行號,JSR-45為這些非Java編寫, 卻需要編譯成位元組碼並執行在Java虛擬機器 中的程式提供了一個進行除錯的標準機制, 使用SourceDebugExtension屬性 就可以用於儲存這個標準所新加入的除錯資訊 |
Syntheitc | 類、方法表、欄位表 | 標識方法或欄位為編譯器自動生成的 |
LocalVariableTypeTable | 類 | JDK1.5中新增的屬性,它使用特徵簽名代替描述符, 是為了引入泛型語法之後能描述泛型引數化型別而新增 |
RuntimeVisibleAnnotations | 類、方法表、欄位表 | JDK1.5中新增的屬性,為動態註解提供支援。 RuntimeVisibleAnnotations屬性用於指明哪些註解是執行時 (實際上就是進行反射呼叫)可見的 |
RuntimeInvisibleAnnotations | 類、方法表、欄位表 | JDK1.5中新增的屬性,與RuntimeVisibleAnnotations屬性 作用剛好相反,用於指明哪些註解是執行時不可見的 |
RuntimeVisibleParameterAnnotations | 方法表 | JDK1.5中新增的屬性,作用與RuntimeVisibleAnnotations 屬性類似,只不過作用物件為方法引數 |
RuntimeInvisibleParameterAnnotations | 方法表 | JDK1.5中新增的屬性,作用與RuntimeInvisibleAnnotations 屬性類似,只不過作用物件為方法引數 |
AnnotationsDefault | 方法表 | JDK1.5中新增的屬性,用於記錄註解類元素的預設值 |
BootstrapMethods | 類檔案 | JDK1.7中新增的屬性,用於儲存invokedynamic指令引用的引導方法限定符 |