1、概述
Class文件是一組以8位字節為基礎單位的二進制流,各個數據項目嚴格按照順序緊湊的排列在Class文件中,中間沒有添加任何的分隔符,這使得整個Class文件中存儲的內容幾乎全部是程序運行的必要數據。當遇到需要占用8個字節以上空間的數據項時,則會按照高位在前的方式分隔成多個8位字節進行存儲。
Class文件采用一種類似於C語言結構體的偽結構來存儲數據,這種結構中只存在兩種數據類型:無符號數和表。
- 無符號數:屬於基本數據類型,以u1、u2、u4、u8來分別代表1個字節、2個字節、4個字節和8個字節的無符號數,無符號數可以用來描述數字、索引引用、數量值或者按照UTF-8編碼構成字符串值
- 表:由多個無符號數或其他表作為數據項構成的符合數據類型,所以表都習慣以_info結尾。表用於描述有層次關系的復合結構的數據。整個Class文件本質上就是一張表,它由下表所示的數據項構成:
表1、表結構數據項

2、Class文件構成
2.1 魔數與class文件版本號
- 每個Class文件的頭4個字節成為魔數(Magic Number),它的唯一作用是確定這個文件是否為一個能被虛擬機接受的Class文件。Class文件的魔數值為:0xCAFEBABE。
- 緊接着魔數的4個字節存儲的是Class文件的版本號:5、6兩個字節是次版本號(Minor Version),7、8兩個字節是主版本號(Major Version)。下表是常見的Class文件版本號;
表2、常見class文件版本號

2.2 常量池
常量池中的每一個常量都是一個表,在jdk1.7中共有14中表結構數據,這14中標開始的第一位是一個u1類型的標記位,具體含義如下表所示:
表3、常量池中表結構類型

2.2.1 常量池位置
- 緊接着主次版本之后就是常量池,常量池可以理解為Class文件之中的資源倉庫,它是以Class文件結構中與其他項目關聯最多的數據類型,也是占用文件空間最大的數據項目之一,同時它還是在Class文件中第一個出現的表結構的數據項目。
- 在常量池的入口會放置一項u2類型的數據用來代表常量池的容量計數值(constant_pool_count),並且這個容量計數是從1開始的。
2.2.2 常量池中的數據類型
- 字面量(Literal):主要存放一些文本字符串和聲明為final的常量
- 符號引用(Symbolic References):主要包括類和接口的全限定名(Fully Qualified Name)、字段的名稱和描述符(Descriptor)、方法的名稱和描述符
2.2.3 各種表結構
表4、常量池中各種表結構



2.3 訪問標志
在常量池結束之后,緊接着的兩個字節代表着訪問標志(access_flags),這個標志用於識別一些類或接口層次的訪問信息,包括:這個Class是類還是接口;是否定義為public類型;是否定義為abstract類型;如果是類的話是否聲明為final等。具體的標志位以及含義如下表所示:
表5、類文件訪問屬性

2.4 類索引、父類索引與接口索引集合
類索引、父類索引和接口索引集合都按順序排列在訪問標志之后,並且都是一個u2類型的數據項。類索引和父類索引用兩個u2類型的索引值表示,它們各自指向一個類型為CONSTANT_Class_info的類描述符常量,通過CONSTANT_Class_info類型的常量中的索引值可以找到定義在CONSTANT_Utf8_info類型的常量中的全限定名字符串。類索引(this_class)和父類索引(super_class)都是一個u2類型的數據,而接口索引集合(interfaces)是一組u2類型的數據的集合,Class文件中由這三項數據來確定這個類的繼承關系。
2.5 字段表集合
字段表(field_info)用於描述接口或類中聲明的字段。字段(field)包括類級變量和實例級變量,但不包括在方法內部聲明的局部變量。字段表的格式如下:
表6、字段表格式

字段修飾符放在access_flags項目中,它與類中的access_flags項目是非常類似的,都是一個u2的數據類型,其中可以設置的標志位和含義見下表:
表7、字段表訪問修飾符

跟隨access_flags標志的是兩項索引值:name_index和descriptor_index。它們都是對常量池的引用,分別代表着字段的簡單名稱以及字段和方法的描述符。根據描述符規則,基本數據類型以及代表無返回值類型的void類型都用一個大寫字母表示,而對象類型則是字符L加對象的全限定名表示,詳見下表:
表8、對象類型標識符

2.6 方法表集合
Class文件存儲格式中對方法的描述與對字段的描述幾乎采用了完全一致的方式,方法表的字段如同字段表的一樣,依次是訪問標志(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表集合(attributes)。方法表屬性如下表所示:
表9、方法表屬性

對於方法表,所有標志位及其取值參考下表:
表10、方法表標志位列表

2.7 屬性表集合
在Class文件、字段表、方法表都可以攜帶自己的屬性表集合,用於描述某些場景的專有信息。屬性表中不要求各個屬性表具有嚴格的順序,只要不與已有屬性重名即可。下表列舉了一些java虛擬機預定的屬性。
表11、虛擬機屬性表類型



對於每個屬性,它的名稱需要從常量池中引用一個CONSTANT_Utf8_info類型的常量來表示,而屬性值的格式則是完全自定義,只需要通過一個u4的長度屬性去說明屬性值所占用的位數即可。屬性表的的結構應滿足下表中所示結構:
表12、屬性表結構

2.7.1 Code屬性
java中方法體在經過編譯之后最終以字節的形式存放在Code屬性內。Code屬性出現在方法表的屬性集合之中(不包括抽象類或接口的方法),Code屬性表結構如下:
表13、Code屬性表結構

- attribute_name_index:一項指向CONSTANT_Utf8_info型常量的索引,常量值固定為”Code“,它代表該屬性的屬性名稱。
- attribute_length:屬性值的長度,由於屬性名稱索引與屬性長度一共6個字節,所以屬性值長度=屬性表長度-6
- max_stack:代表了操作數棧(Operand Stacks)深度的最大值。在方法執行的任意時刻,操作數棧都不會超過這個最大值。虛擬機運行的時候需要根據這個值來分配棧幀(Stack Frame)中的操作棧深度
- max_locals:局部變量表所需要的存儲空間。單位是Slot
- code_length:字節碼長度
- code:編譯后生成的字節碼指令
- exception_table:包含4個字段(start_pc、end_pc、handler_pc、catch_type)。這些字段的含義是:當字節碼在start_pc行到end_pc行之間(try的范圍)出現了類型為catch_type的異常或其子類的異常(catch_type為指向一個CONSTANT_Class_info型常量的索引),則跳轉到handler_pc行繼續處理。
2.7.2 Exceptions屬性
Exception屬性是和Code屬性平級的一項屬性,它的結構如下表所示:
表14、Exceptions表屬性結構

number_of_exceptions表示方法可能拋出number_of_exceptions中受查異常,每一種受查異常用一個exception_index_table項表示,exception_index_table是一個指向常量池中CONSTANT_Class_info型常量的索引,代表了該受查異常的類型
2.7.3 LineNumberTable屬性
LineNumberTable屬性用於描述java源碼行號和字節碼行號之間的對應關系。它並不是運行時必須的屬性,但默認會生成到Class文件中,可以再javac中分別使用-g:none或-g:lines選項取消或要求生成這項信息。如果選擇不生成LineNumberTable屬性,當程序拋出異常時,堆棧中將不會顯示出錯的行號,並且在調試程序的時候,也無法按照源碼行設置斷點,其結構如下表所示:
表15、LineNumberTable屬性表結構

line_number_table是一個數量為line_number_table_length、類型為line_number_info的集合,line_number_info表包含了start_pc和line_number兩個u2類型的數據項,前者表示字節碼行號,后者表示java源碼行號
2.7.4 LocalVariableTable屬性
LocalVariableTable屬性用於描述棧幀中局部變量表中的變量與java源碼中定義的變量的關系,它也不是運行時必須數據,但默認會生成在Class文件中。可以在javac時使用-g:none或-g:lines來取消或要求生成這項信息。如果不生成這個信息,當其他人引入這個方法時,所有參數名稱會丟失,IDE會使用諸如arg1、arg2之類的占位符替代原有的參數名,這對程序運行沒有影響,但是對代碼編寫帶來較大不便,而且在調試期間無法根據參數名稱從上下文中獲取參數值。其結構如下表所示:
表16、LocalVariableTable屬性表結構

其中local_variable_info 項代表了一個棧幀與源碼中局部變量的關聯,local_variable_info表結構如下所示:
表17、local_variable_info表結構

- start_pc:局部變量的生命周期開始的字節碼偏移量
- length:局部變量在生命周四開始的字節碼的作用范圍覆蓋長度
- name_index:指向常量池中CONSTANT_Utf8_info型常量的索引,代表局部變量的名稱
- descriptor_index:指向常量池中CONSTANT_Utf8_info型常量的索引,代表局部變量的描述符
- index:這個局部變量在棧幀局部變量表中Slot的位置
2.7.5 SourceFile屬性
SourceFile屬性用於記錄生成這個Class文件的源碼名稱。這個屬性也是可選的,可以分別使用javac 的 -g :none或-g:source來關閉或要求生成這項信息。如果不生成這項信息,當拋出異常,堆棧中將不會顯示出錯代碼所屬的文件名。這個屬性是一個定長的屬性,其結構如下:
表18、SourceFile屬性表結構

sourcefile_index數據項時指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源碼文件名
2.7.6 ConstantValue屬性
ConstantValue屬性的作用是通知虛擬機自動為靜態變量賦值。只有被static修飾的變量才可以使用這個屬性。
2.7.7 InnerClasses屬性
InnerClasses屬性用於記錄內部類和宿主類之間的關系。如果一個類中定義了內部類,那編譯器將會為它以及它包含的內部類生成InnerClasses屬性,該屬性結構如下圖所示:
表19、InnerClasses屬性表結構

數據項number_of_classes代表需要記錄多少個內部類信息,每個內部類的信息都由一個inner_classes_info表進行描述,inner_classes_info表結構如下:
表20、inner_classes_info表結構

- inner_class_info_index:指向常量池中CONSTANT_Class_info類型常量的索引,代表內部類的符號引用
- outer_class_info_index:指向常量池中CONSTANT_Class_info類型常量的索引,代表宿主類的符號引用
- inener_name_index:指向常量池中CONSTANT_Utf8_info型常量的索引,代表內部類的名稱,如果是匿名內部類,那么這項值為0
- inner_class_access_flags:內部類的訪問標志,它的取值范圍見下表:
表21、內部類訪問標志

2.7.8 Deprecated及Synthetic屬性
Deprecated及Synthetic屬性都屬於標志類型的布爾屬性,只存在有和沒有的區別,沒有屬性值概念。Deprecated屬性用於表示某個類、字段或方法,它可以通過在代碼中使用@deprecated注釋進行設置。Synthetic屬性代表此字段或方法不是由java源碼直接產生,而是由編譯器自行添加的。Deprecated和Synthetic屬性的結構如下:
表22、Deprecated和Synthetic屬性的結構

2.7.9 StackMapTable屬性
StackMapTable屬性在JDK1.6發布后增加到Class文件規范中,它是一個復雜的變長屬性,位於Code屬性的屬性表中。這個屬性會在虛擬機的類加載的字節碼驗證階段被新類型檢查驗證器(Type Checker)使用,目的在於替代以前比較消耗性能的基於數據流分析的類型推導驗證器。StackMapTable屬性中包含零至多個棧映射幀(Stack Map Frames),每個棧幀射幀都顯式或隱式的代表了一個字節碼的偏移量,用於表示執行到該字節碼時局部變量表和操作數棧的驗證類型。類型檢查器會通過檢查目標方法的局部變量和操作數棧所需要的類型來確定指令是否符合邏輯的約束。其結構如下:
表23、StackMapTable屬性表結構

2.7.10 Signature屬性
Signature屬性在JDK1.5發布后增加到Class文件規范中,它是一個可選的定長屬性,可以出現於類、屬性表和方法表結構的屬性表中。在任何類、接口、初始化方法或成員的泛型簡明中如果包含了類型變量()或參數化類型(),則Signature屬性會為它記錄泛型簽名信息。Signature屬性的結構如下所示:
表24、Signature屬性表結構

- signature_index:必須是一個對常量池的有效索引。常量池在該索引未知的項必須是CONSTANT_Utf8_info結構,表示類簽名、方法類型簽名或字段類型簽名。 如果當前的Signature屬性是類文件的屬性,則表示是類簽名;如果是方法表的屬性則表示是方法類型簽名;如果是字段表屬性則說明是字段類型簽名。
2.7.11 BootstrapMethods屬性
BootstrapMethods屬性是在JDK1.7發布后增加到CLass文件規范中,它是一個復雜的變長屬性,位於類文件表中。這個屬性用於保存invokedynamic指令引用的引導方法限定符。如果某個類文件的常量池中曾經出現過CONSTANT_InvokeDynamic_info類型的常量,那么這個類文件的屬性表中必須存在一個明確的BootstrapMethods屬性,另外,即使CONSTANT_InvokeDynamic_info類型的常量在常量池中出現過多次,最多也只能有一個BootstrapMethods屬性。BootstrapMethods屬性結構如下:
表25、BootstrapMethods屬性結構

其中引用到的bootstrap_method結構如下:
表26bootstrap_method結構

