無關性
Oracle公司以及其他虛擬機發行商發佈過許多可以運行在各種不同硬件平臺和操作系統上的Java虛擬機,這些虛擬機都可以載入和執行同一種平臺無關的字節碼,從而實現了程序的“一次編寫,到處運行”的平臺無關性。
實現語言無關性的基礎仍然是虛擬機和字節碼存儲格式,Java虛擬機不與任何程序語言(包括Java)綁定,它只與“Class文件”這種特定的二進制文件格式所關聯。Class文件中包含了Java虛擬機指令集、符號表以及若干其他輔助信息。基於安全方面的考慮,《Java虛擬機規範》中要求在Class文件必須應用許多強制性的語法和結構化約束,但圖靈完備的字節碼格式,保證了任意一門功能性語言都可以表示爲一個能被Java虛擬機所接受的有效的Class文件。作爲一個通用的、與機器無關的執行平臺,任何其他語言的實現者都可以將Java虛擬機作爲他們語言的運行基礎,以Class文件作爲他們產品的交付媒介。
例如,使用Java編譯器可以把Java代碼編譯爲存儲字節碼的Class文件,使用JRuby等其他語言的編譯器一樣可以把它們的源程序代碼編譯成Class文件。虛擬機不關心Class的來源是什麼語言,它與程序語言之間的關係如圖所示:
Class類文件結構
Class文件是一組以8個字節爲基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在文件之中,中間沒有添加任何分隔符,這使得整個Class文件中存儲的內容幾乎全部是程序運行的必要數據,沒有空隙存在。當遇到需要佔用8個字節以上空間的數據項時,則會按照高位在前的方式分割成若干個8個字節進行存儲。
根據《Java虛擬機規範》的規定,Class文件格式採用一種類似於C語言結構體的僞結構來存儲數據,這種僞結構中只有兩種數據類型:“無符號數”和“表”。後面的解析都要以這兩種數據類型爲基礎。
- 無符號數屬於基本的數據類型,以u1、u2、u4、u8來分別代表1個字節、2個字節、4個字節和8個字節的無符號數,無符號數可以用來描述數字、索引引用、數量值或者按照UTF-8編碼構成字符串值。
- 表是由多個無符號數或者其他表作爲數據項構成的複合數據類型,爲了便於區分,所有表的命名都習慣性地以“_info”結尾。表用於描述有層次關係的複合結構的數據,整個Class文件本質上也可以視作是一張表,這張表由表6-1所示的數據項按嚴格順序排列構成。
類型 | 名稱 | 中文名 | 數量 |
---|---|---|---|
u4 | magic | 文件標誌,魔數 | 1 |
u2 | minor_version | 次版本號 | 1 |
u2 | major_version | 主版本號 | 1 |
u2 | constant_pool_count | 常量池容量計數值 | 1 |
cp_info | constant_pool | 常量池 | constant_pool_count-1 |
u2 | access_flags | 訪問標誌 | 1 |
u2 | this_class | 當前類 | 1 |
u2 | super_class | 父類索引 | 1 |
u2 | interfaces_count | 接口數量 | 1 |
u2 | interfaces | 接口集合 | interfaces_count |
u2 | fields_count | 字段數量 | 1 |
field_info | fields | 字段表集合 | fields_count |
u2 | methods_count | 方法數量 | 1 |
method_info | methods | 方法表集合 | methods_count |
u2 | attributes_count | 屬性數量 | 1 |
attribute_info | attributes | 屬性表集合 | attributes_count |
無論是無符號數還是表,當需要描述同一類型但數量不定的多個數據時,經常會使用一個前置的容量計數器加若干個連續的數據項的形式,稱這一系列連續的某一類型的數據爲某一類型的“集合”。
魔數與Class文件版本
每個Class文件的頭4個字節稱爲魔數(magic),它的唯一作用是判斷該文件是否爲一個能被虛擬機接受的Class文件。它的值固定爲0xCAFEBABE。
緊接着magic的4個字節存儲的是Class文件的次版本號和主版本號,高版本的JDK能向下兼容低版本的Class文件,但不能運行更高版本的Class文件。
常量池
主、次版本號之後是常量池入口,是Class文件結構中與其他項目關聯最多的數據,通常也是佔用Class文件空間最大的數據項目之一,還是在Class文件中第一個出現的表類型數據項目。
常量池是從1開始索引的,因爲第0項是用於某些指向常量池的索引值的數據在特定情況下表示“不引用任何一個常量池項目”的含義。
常量池中主要存放兩大類常量:字面量(Literal)和符號引用(SymbolicReferences)。
字面量比較接近於Java語言層面的常量概念,如文本字符串、被聲明爲final的常量值等。而符號引用則屬於編譯原理方面的概念,主要包括下面幾類常量:
- 被模塊導出或者開放的包(Package)
- 類和接口的全限定名(Fully Qualified Name)
- 字段的名稱和描述符(Descriptor)
- 方法的名稱和描述符
- 方法句柄和方法類型(Method Handle、Method Type、InvokeDynamic)
- 動態調用點和動態常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
當虛擬機做類加載時,將會從常量池獲得對應的符號引用,再在類創建時或運行時解析、翻譯到具體的內存地址之中。常量表中分別有17種不同類型的常量,這17類表都有一個共同的特點,表結構起始的第一位是個u1類型的標誌位(tag),代表着當前常量屬於哪種常量類型,17種常量類型所代表的具體含義如表所示:
類型 | 標誌(tag) | 描述 |
---|---|---|
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_MothodType_info | 16 | 表示方法類型 |
CONSTANT_Dynamic_info | 17 | 表示一個動態計算常量 |
CONSTANT_InvokeDynamic_info | 18 | 表示一個動態方法調用點 |
CONSTANT_Module_info | 19 | 表示一個模塊 |
CONSTANT_Package_info | 20 | 表示一個模塊中開放或者導出的包 |
17種常量項的結構定義總結如下:
常量 | 項目 | 類型 | 描述 |
CONSTANT_utf8_info | tag | u1 | 值爲1 |
legnth | 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_Dynamic_info | tag | u1 | 值爲17 |
bootstrap_method_attr_index | u2 | 值必須是對當前Class文件中引導方法表的bootstrap_method[]數組的有效索引 | |
name_and_type_index | u2 | 值必須是對當前常量池的有效索引,常量池在該索引處的項必須是CONSTANT_NameAndType_info結構表示方法名和方法描述符 | |
CONSTANT_InvokeDynamic_info | tag | u1 | 值爲18 |
bootstrap_method_attr_index | u2 | 值必須是對當前Class文件中引導方法表的bootstrap_method[]數組的有效索引 | |
name_and_type_index | u2 | 值必須是對當前常量池的有效索引,常量池在該索引處的項必須是CONSTANT_NameAndType_info結構表示方法名和方法描述符 | |
CONSTANT_Module_info | tag | u1 | 值爲19 |
name_index | u2 | 值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示模塊名稱 | |
CONSTANT_Package_info | tag | u1 | 值爲20 |
name_index | u2 | 值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示包名稱 |
訪問標誌
常量池結束緊接着2個字節代表訪問標誌(access_flags),這個標誌用於識別一些類或者接口層次的訪問信息,包括:這個Class是類還是接口;是否定義爲public類型;是否定義爲abstract類型;如果是類的話,是否被聲明爲final等等。
標誌名稱 | 標誌值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x001 | 是否爲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 | 標識這是一個枚舉 |
ACC_MODULE | 0x8000 | 標識這是一個模塊 |
access_flags中一共有16個標誌位可以使用,當前只定義了其中9個,沒有使用到的標誌位要求一律爲零。
類索引、父類索引與接口索引集合
類索引(this_class)和父類索引(super_class)都是一個u2類型的數據,而接口索引集合(interfaces)是一組u2類型的數據的集合,Class文件中由這三項數據來確定該類型的繼承關係。
類索引用於確定這個類的全限定名,父類索引用於確定這個類的父類的全限定名。除了java.lang.Object之外,所有的Java類都有父類,父類索引都不爲0。
接口索引集合就用來描述這個類實現了哪些接口,這些被實現的接口將按implements關鍵字(如果這個Class文件表示的是一個接口,則應當是extends關鍵字)後的接口順序從左到右排列在接口索引集合中。
類索引、父類索引和接口索引集合都按順序排列在訪問標誌之後,類索引和父類索引用兩個u2類型的索引值表示,它們各自指向一個類型爲CONSTANT_Class_info的類描述符常量,通過CONSTANT_Class_info類型的常量中的索引值可以找到定義在CONSTANT_Utf8_info類型的常量中的全限定名字符串。
對於接口索引集合,入口的第一項u2類型的數據爲接口計數器(interfaces_count),表示索引表的容量。如果該類沒有實現任何接口,則該計數器值爲0,後面接口的索引表不再佔用任何字節。
字段表集合
字段表(field_info)用於描述接口或者類中聲明的變量。Java語言中的“字段”(Field)包括類級變量以及實例級變量,但不包括在方法內部聲明的局部變量。
類型 | 名稱 | 數量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
字段修飾符放在access_flags項目中,它與類中的access_flags項目是非常類似的,都是一個u2的數據類型,其中可以設置的標誌位和含義。
標誌名稱 | 標誌值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 字段是否public |
ACC_PRIVATE | 0x0002 | 字段是否private |
ACC_PROTECTED | 0x0004 | 字段是否protected |
ACC_STATIC | 0x0008 | 字段是否static |
ACC_FINAL | 0x0010 | 字段是否final |
ACC_VOLATILE | 0x0040 | 字段是否volatile |
ACC_TRANSIENT | 0x0080 | 字段是否transient |
ACC_SYNTHETIC | 0x1000 | 字段是否由編譯器自動生成的 |
ACC_ENUM | 0x4000 | 字段是否enum |
name_index和descriptor_index它們都是對常量池項的引用,分別代表着字段的簡單名稱以及字段和方法的描述符。
簡單名稱”,“描述符”以及“全限定名”三種特殊字符串的概念:
- 類的全限定名,僅僅是把類全名中的“.”替換成了“/”而已,爲了使連續的多個全限定名之間不產生混淆,在使用時最後一般會加入一個“;”號表示全限定名結束。
- 簡單名稱則就是指沒有類型和參數修飾的方法或者字段名稱,假設類中的inc()方法和m字段的簡單名稱分別就是“inc”和“m”。
- 描述符的作用是用來描述字段的數據類型、方法的參數列表(包括數量、類型以及順序)和返回值。根據描述符規則,基本數據類型(byte、char、double、float、int、long、short、boolean)以及代表無返回值的void類型都用一個大寫字符來表示,而對象類型則用字符L加對象的全限定名來表示。對於數組類型,每一維度將使用一個前置的“[”字符來描述,如一個定義爲“java.lang.String[][]”類型的二維數組將被記錄成“[[Ljava/lang/String;”,一個整型數組“int[]”將被記錄成“[I”。
方法表集合
Class文件存儲格式中對方法的描述與對字段的描述採用了幾乎完全一致的方式,方法表的結構如同字段表一樣,依次包括訪問標誌(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表集合(attributes)幾項。
類型 | 名稱 | 數量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
訪問標誌和屬性表集合的可選項中有所區別,如下表:
標誌名稱 | 標誌值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 方法是否public |
ACC_PRIVATE | 0x0002 | 方法是否private |
ACC_PROTECTED | 0x0004 | 方法是否protected |
ACC_STATIC | 0x0008 | 方法是否static |
ACC_FINAL | 0x0010 | 方法是否final |
ACC_SYNCHRONIZED | 0x0020 | 方法是否爲synchronized |
ACC_BRIDGE | 0x0040 | 方法是否是由編譯器產生的橋接方法 |
ACC_VARARGS | 0x0080 | 方法是否接受不定參數 |
ACC_NATIVE | 0x0100 | 方法是否爲native |
ACC_ABSTRACT | 0x0400 | 方法是否爲abstract |
ACC_STRICTFP | 0x0800 | 方法是否爲strictfp |
ACC_SYNTHETIC | 0x1000 | 方法是否由編譯器自動生成的 |
屬性表集合
屬性表(attribute_info),Class文件、字段表、方法表都可以攜帶自己的屬性表集合,以描述某些場景專有的信息。與Class文件中其他的數據項目要求嚴格的順序、長度和內容不同,屬性表集合的限制稍微寬鬆一些,不再要求各個屬性表具有嚴格順序,並且《Java虛擬機規範》允許只要不與已有屬性名重複,任何人實現的編譯器都可以向屬性表中寫入自己定義的屬性信息,Java虛擬機運行時會忽略掉它不認識的屬性。
屬性名稱 | 使用位置 | 含義 |
---|---|---|
Code | 方法表 | Java代碼編譯成的字節碼指令 |
ConstantValue | 字段表 | final關鍵字定義的常量值 |
Deprecated | 類、方法表、字段表 | 被聲明爲deprecated的方法和字段 |
Exceptions | 方法表 | 方法拋出的異常 |
EnclosingMethod | 類文件 | 僅當一個類爲局部類或者匿名類時才能擁有這個屬性,這個屬性用於標識這個類所在的外圍方法 |
InnerClasses | 類文件 | 內部類列表 |
LineNumberTable | Code屬性 | Java源碼的行號與字節碼指令的對應關係 |
LocalVariableTable | Code屬性 | 方法的局部變量描述 |
StackMapTable | Code屬性 | JDK 6中新增的屬性,供新的類型檢查驗證器(Type Checker)檢查和處理目標方法的局部變量和操作數棧所需要的類型是否匹配 |
Signature | 類、方法表、字段表 | JDK 5中新增的屬性,這個屬性用於支持泛型情況下的方法簽名,在Java語言中,任何類、接口、初始化方法或成員的泛型簽名如果包含了類型變量(Type Variables)或參數化類型(Parameterized Types),則Signature屬性會爲它記錄泛型簽名信息。由於Java泛型採用擦除法實現,在爲了避免類型信息被擦除後導致簽名混亂,需要用這個屬性記錄泛型中的相關信息 |
SourceFile | 類文件 | 記錄源文件名稱 |
SourceDebugExtension | 類文件 | JDK 6中新增的屬性,SourceDebugExtension用於存儲額外的調試信息。 |
Synthetic | 類、方法表、字段表 | 標識方法或字段爲編譯器自動生成 |
LocalVariableTypeTable | 類 | JDK 5中新增的屬性,它使用特徵前面代替描述符,是爲了引入泛型語法之後能描述泛型參數化類型而添加 |
RuntimeVisibleAnnotations | 類、方法表、字段表 | JDK 5中新增的屬性,爲動態註解提供支持。用於知名哪些註解是運行時可見的 |
RuntimeInvisibleAnnotations | 類、方法表、字段表 | JDK 5中新增的屬性,與RuntimeVisibleAnnotations作用正好相反,用於指明哪些註解是運行時不可見的 |
RuntimeVisibleParameterAnnotations | 方法表 | JDK 5中新增的屬性,作用與RuntimeVisibleAnnotations類似,不過作用對象是方法參數 |
RuntimeInvisibleParameterAnnotations | 方法表 | JDK 5中新增的屬性,作用與RuntimeInvisibleAnnotations類似,不過作用對象是方法參數 |
AnnotationsDefault | 方法表 | JDK 5中新增的屬性,用於記錄註解類元素的默認值 |
BootstrapMethods | 類文件 | JDK 7中新增的屬性,用於保存invokedynamic指令引用的引導方法限定符 |
RunntimeVisibleTypeAnnotations | 類、方法表、字段表,Code屬性 | JDK 8中新增的屬性,爲實現JSR 308中新增的類型註釋提供支持,用於用於指明哪些類註釋是運行時(實際上運行時就是進行反射調用)可見的 |
RunntimeInvisibleTypeAnnotations | 類、方法表、字段表,Code屬性 | JDK 8中新增的屬性,爲實現JSR 308中新增的類型註釋提供支持,用於用於指明哪些類註釋是運行時不可見的 |
MethodParameters | 方發表 | JDK 8新增屬性,用於支持(編譯時加上-parameters參數)將方法名稱編譯進Class文件中,並可運行時獲取。此前要獲取方法名稱只能通過JavaDoc中得到 |
Module | 類 | JDK 9新增屬性,用於記錄一個Module的名稱以及相關信息(requires、exports、opens、uses、provides) |
ModulePackages | 類 | JDK 9新增屬性,用於記錄一個模塊中所有被exports或者opens的包 |
ModuleMainClass | 類 | JDK 9新增屬性,用於指定一個模塊的主類 |
NestHost | 類 | JDK 11新增屬性,用於支持嵌套類(Java內部類)的反射和控制訪問的API,一個內部類通過該屬性的值自己的宿主類 |
NestMembers | 類 | JDK 11新增屬性,用於支持嵌套類(Java內部類)的反射和訪問控制的API,一個宿主類通過該屬性得知自己有哪些內部類 |
對於每一個屬性,它的名稱都要從常量池中引用一個CONSTANT_Utf8_info類型的常量來表示,而屬性值的結構則是完全自定義的,只需要通過一個u4的長度屬性去說明屬性值所佔用的位數即可。
一個符合規則的屬性表應該滿足如圖結構:
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attributes_length | 1 |
u1 | info | attributes_length |
1)Code屬性
Java程序方法體中的代碼講過Javac編譯後,生成的字節碼指令便會存儲在Code屬性中,但並非所有的方法表都必須存在這個屬性,比如接口或抽象類中的方法就不存在Code屬性。如果方法表有Code屬性存在,那麼它的結構將如下表所示:
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | max_stack | 1 |
u2 | max_local | 1 |
u4 | code_length | 1 |
u1 | code | code_length |
u2 | exception_table_length | 1 |
exception_info | exception_table | exception_table_length |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
-
attribute_name_index是一項指向CONSTANT_Utf8_info型常量的索引,常量值固定爲“Code”,它代表了該屬性的名稱。attribute_length指示了屬性值的長度,由於屬性名稱索引與屬性長度一共是6個字節,所以屬性值的長度固定爲整個屬性表的長度減去6個字節。
-
max_stack代表了操作數棧深度的最大值,max_locals代表了局部變量表所需的存儲空間,它的單位是Slot,並不是在方法中用到了多少個局部變量,就把這些局部變量所佔Slot之和作爲max_locals的值,原因是局部變量表中的Slot可以重用。
-
code_length和code用來存儲Java源程序編譯後生成的字節碼指令。code用於存儲字節碼指令的一系列字節流,它是u1類型的單字節,因此取值範圍爲0x00到0xFF,那麼一共可以表達256條指令,目前,Java虛擬機規範已經定義了其中200條編碼值對應的指令含義。code_length雖然是一個u4類型的長度值,理論上可以達到2^32-1,但是虛擬機規範中限制了一個方法不允許超過65535條字節碼指令,如果超過了這個限制,Javac編譯器將會拒絕編譯。
-
字節碼指令之後是這個方法的顯式異常處理表集合(exception_table),它對於Code屬性來說並不是必須存在的。它包含四個字段:
類型 | 名稱 | 數量 |
---|---|---|
u2 | start_pc | 1 |
u2 | end_pc | 1 |
u2 | handler_pc | 1 |
u2 | catch_type | 1 |
這些字段的含義爲:如果字節碼從第start_pc行到第end_pc行之間(不含end_pc行)出現了類型爲catch_type或其子類的異常(catch_type爲指向一個CONSTANT_Class_info型常量的索引),則轉到第handler_pc行繼續處理,當catch_pc的值爲0時,代表人和的異常情況都要轉到handler_pc處進行處理。異常表實際上是Java代碼的一部分,編譯器使用異常表而不是簡單的跳轉命令來實現Java異常即finally處理機制,也因此,finally中的內容會在try或catch中的return語句之前執行,並且在try或catch跳轉到finally之前,會將其內部需要返回的變量的值複製一份副本到最後一個本地表量表的Slot中。
Code屬性是Class文件中最重要的一個屬性,如果把一個Java程序中的信息分爲代碼和元數據兩部分,那麼在整個Class文件裏,Code屬性用於描述代碼,所有的其他數據項目都用於描述元數據。
2)Exception屬性
列舉出方法中可能拋出的受查異常,也就是方法描述時在throws關鍵字後面列舉的異常。
3)LineNumberTable屬性
用於描述棧幀中局部變量表的變量與Java源碼中定義的變量之間的關係
4)LocalVariableTable及LocalVariableTypeTable屬性
LocalVariableTable屬性用於描述棧幀中局部變量表的變量與Java源碼中定義的變量之間的關係,
在JDK 5引入泛型之後,增加了LocalVariableTypeTable屬性,使用字段的特徵簽名來完成泛型的描述。
5)SourceFile及SourceDebugExtension屬性
SourceFile屬性用於記錄生成這個Class文件的源碼文件名稱。在JDK 5時,新增了SourceDebugExtension屬性用於存儲額外的代碼調試信息。
6)ConstantValue屬性
ConstantValue屬性的作用是通知虛擬機自動爲靜態變量賦值。只有被static關鍵字修飾的變量(類變量)纔可以使用這項屬性。
對非static類型的變量(也就是實例變量)的賦值是在實例構造器
Oracle公司實現的Javac編譯器的選擇是,如果同時使用final和static來修飾一個變量(按照習慣,這裏稱“常量”更貼切),並且這個變量的數據類型是基本類型或者java.lang.String的話,就將會生成ConstantValue屬性來進行初始化;如果這個變量沒有被final修飾,或者並非基本類型及字符串,則將會選擇在
7)InnerClasses屬性
InnerClasses屬性用於記錄內部類與宿主類之間的關聯。如果一個類中定義了內部類,那編譯器將會爲它以及它所包含的內部類生成InnerClasses屬性。
8)Deprecated屬性和Synthetic屬性
Deprecated和Synthetic兩個屬性都屬於標誌類型的布爾屬性,只存在有和沒有的區別,沒有屬性值的概念。
Deprecated屬性用於表示某個類、字段或者方法,已經被程序作者定爲不再推薦使用,它可以通過代碼中使用“@deprecated”註解進行設置。
Synthetic屬性代表此字段或者方法並不是由Java源碼直接產生的,而是由編譯器自行添加的。
9)StackMapTable屬性
StackMapTable屬性在JDK 6增加到Class文件規範之中,它是一個相當複雜的變長屬性,位於Code屬性的屬性表中。這個屬性會在虛擬機類加載的字節碼驗證階段被新類型檢查驗證器(Type Checker)使用,目的在於代替以前比較消耗性能的基於數據流分析的類型推導驗證器。
10)Signature屬性
Signature屬性在JDK 5增加到Class文件規範之中,它是一個可選的定長屬性,可以出現於類、字段表和方法表結構的屬性表中。
11)BootstrapMethods屬性BootstrapMethods屬性
在JDK 7時增加到Class文件規範之中,它是一個複雜的變長屬性,位於類文件的屬性表中。這個屬性用於保存invokedynamic指令引用的引導方法限定符。
12)MethodParameters屬性
MethodParameters是在JDK 8時新加入到Class文件格式中的,它是一個用在方法表中的變長屬性,作用是記錄方法的各個形參名稱和信息。
13)模塊化相關屬性
JDK 9的一個重量級功能是Java的模塊化功能,因爲模塊描述文件(module-info.java)最終是要編譯成一個獨立的Class文件來存儲的,所以,Class文件格式也擴展了Module、ModulePackages和ModuleMainClass三個屬性用於支持Java模塊化相關功能。
Module屬性是一個非常複雜的變長屬性,除了表示該模塊的名稱、版本、標誌信息以外,還存儲了這個模塊requires、exports、opens、uses和provides定義的全部內容
ModulePackages是另一個用於支持Java模塊化的變長屬性,它用於描述該模塊中所有的包,不論是不是被export或者open的。
ModuleMainClass屬性是一個定長屬性,用於確定該模塊的主類(Main Class)。
14)運行時註解相關屬性
JDK 5時期,Java語言的語法進行了多項增強,其中之一是提供了對註解(Annotation)的支持。爲了存儲源碼中註解信息,Class文件同步增加了RuntimeVisibleAnnotations、RuntimeInvisibleAnnotations、RuntimeVisibleParameterAnnotations和RuntimeInvisibleParameter-Annotations四個屬性。JDK 8,進一步加強了Java語言的註解使用範圍,又新增類型註解(JSR 308),所以Class文件中也同步增加了RuntimeVisibleTypeAnnotations和RuntimeInvisibleTypeAnnotations兩個屬性。這六個屬性不論結構還是功能都比較雷同,以RuntimeVisibleAnnotations爲例:
RuntimeVisibleAnnotations是一個變長屬性,它記錄了類、字段或方法的聲明上記錄運行時可見註解,當我們使用反射API來獲取類、字段或方法上的註解時,返回值就是通過這個屬性來取到的。
以上就是《深入理解Java虛擬機》關於類文件結構的內容總結,想更詳細地瞭解的話,可以進行書籍的進一步閱讀。
歡迎點贊/評論,你們的贊同和鼓勵是我寫作的最大動力!