Class檔案結構全面解析(下)
接上回書
書接上一回,分享了Class檔案的主要構成,同時也詳細分析了魔數、次版本號、主版本號、常量池集合、訪問標誌的構造,接下來我們就繼續學習。
歡迎關注微信公眾號:萬貓學社,每週一分享Java技術乾貨。
類索引和父類索引
類索引(this_class)和父類索引(super_class)都是一個u2型別的資料,類索引用於確定這個類的全限定名,父類索引用於確定這個類的父類全限定名。由於java語言不允許多重繼承,所以父類索引只有一個。
類索引和父類索引各自指向常量池中型別為CONSTANT_Class_info的類描述符,再通過類描述符中的索引值找到常量池中型別為CONSTANT_Utf8_info的字串。再來看一下之前的Class檔案例子:
歡迎關注微信公眾號:萬貓學社,每週一分享Java技術乾貨。
結合之前javap分析出來的常量池內容:
#3 = Class #17 // OneMoreStudy
#4 = Class #18 // java/lang/Object
#17 = Utf8 OneMoreStudy
#18 = Utf8 java/lang/Object
類索引為0x0003,去常量池裡找索引為3的類描述符,類描述符中的索引為17,再去找索引為17的字串,就是“OneMoreStudy”。
父類索引為0x0004,去常量池裡找索引為4的類描述符,類描述符中的索引為18,再去常量池裡找索引為18的字串,就是“java/lang/Object”。
歡迎關注微信公眾號:萬貓學社,每週一分享Java技術乾貨。
介面索引集合
介面索引集合(interface)是一組u2型別的資料的集合,由於java語言允許實現多個介面,所以介面索引也有多個,它們按照implements語句後的介面順序從左到右依次排列在介面索引集合中。介面索引集合的第一項資料是介面集合計數值(interfaces_count),表示有多少介面索引。如果該類沒有實現任何介面,那麼該計數值為0,後面的介面索引表不佔任何位元組。之前的例子OneMoreStudy類沒有實現任何介面,所以介面集合計數值就是0,如下圖:
歡迎關注微信公眾號:萬貓學社,每週一分享Java技術乾貨。
欄位表集合
欄位表(field_info)是用來描述介面或類中宣告的變數。包括類級變數(靜態變數)和例項級變數(成員變數),但是不包括在方法內部宣告的區域性變數。具體結構如下表:
型別 | 名稱 | 數量 | 描述 |
---|---|---|---|
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是非常類似的,但是標識和含義是不一樣的。具體如下表:
標誌名稱 | 標誌值 | 含義 |
---|---|---|
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 |
歡迎關注微信公眾號:萬貓學社,每週一分享Java技術乾貨。
這裡提到了簡單名稱、描述符,和全限定名有什麼區別呢?稍微說一下。
簡單名稱是沒有型別和引數修飾的方法或欄位名稱,比如OneMoreStudy類中的number欄位和plusOne()方法的簡單名稱分別是“number”和“plusOne”。
全限定名是把類全名中的“.”替換成“/”就可以了,比如java.lang.Object類的全限定名就是“java/lang/Object”。
描述符是用來描述欄位的資料型別、方法的引數列表(包括數量、型別以及順序)和返回值。基礎資料型別和無返回的void型別都有一個大寫字母表示,物件型別用字元L加物件的全限定名來表示,如下表:
標識字元 | 含義 |
---|---|
B | 基本型別byte |
C | 基本型別char |
D | 基本型別double |
F | 基本型別float |
I | 基本型別int |
J | 基本型別long |
S | 基本型別short |
Z | 基本型別boolean |
V | 特殊型別void |
L | 物件型別 如 Ljava/lang/Object |
對於陣列型別,每一維度使用一個前置的“[”字元來描述,比如java.lang.Object[][]的二維資料,就是“[[Ljava/lang/Object”。在描述方法時,按照先引數列表,後返回值的順序描述,引數列表按照嚴格順序放在“()”值中,比如boolean equals(Object anObject),就是“(Ljava/lang/Object)B”。
歡迎關注微信公眾號:萬貓學社,每週一分享Java技術乾貨。
再來看一下之前的Class檔案例子:
OneMoreStudy類中只有一個欄位number,所以欄位計數值為0x0001。欄位number只被private修飾,沒有其他修飾,所以欄位的訪問標誌位為0x0002。欄位的簡單名稱索引是0x0005,去常量池中找索引為5的字串,為“number”。欄位的描述符索引為0x0006,去常量池中找索引為6的字串,為“I”,是基本型別int。以下是常量池相關內容:
#5 = Utf8 number
#6 = Utf8 I
欄位number的屬性計數值為0x0000,也就是沒有需要額外描述的資訊。
欄位表集合中不會列出從父類或者父介面中繼承而來的欄位,但有可能列出原版Java程式碼中沒有的欄位,比如在內部類中為了保持對外部類的訪問性,會自動新增指向外部類例項的欄位。
歡迎關注微信公眾號:萬貓學社,每週一分享Java技術乾貨。
方法表集合
方法表的結構和欄位表的是一樣的,也是依次包括了訪問標誌(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 | 方法是否sychronized |
ACC_BRIDGE | 0x0040 | 方法是否是由編譯器產生的橋接方法 |
ACC_VARARGS | 0x0080 | 方法是否接受不定引數 |
ACC_NATIVE | 0x0100 | 方法是否為native |
ACC_ABSTRACT | 0x0400 | 方法是否為abstract |
ACC_STRICT | 0x0800 | 方法是否為strictfp |
ACC_SYNTHETIC | 0x1000 | 方法是否由編譯器自動產生 |
方法中的Java程式碼,經過編譯器程式設計成位元組碼指令後,放在方法屬性表集合中一個名為“Code”的屬性裡,後面會有更多分享。
歡迎關注微信公眾號:萬貓學社,每週一分享Java技術乾貨。
再來看一下之前的Class檔案例子:
方法計算值為0x0003,表示集合中有兩個方法(編譯器自動新增的無參構造方法和原始碼中的plusOne方法)。第一個方法的訪問標誌是0x0001,表示只有ACC_PUBLIC標誌為true。
名稱索引為0x0007,在常量池中為索引為7的字串為“
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
歡迎關注微信公眾號:萬貓學社,每週一分享Java技術乾貨。
屬性表集合
屬性表(attribute_info)在前面的分享中出現了幾次,在Class檔案、欄位表、方法表都可以有自己的屬性表集合,用來描述某些場景下特有的資訊。
屬性表不在要求具有嚴格的順序,並且只要不與已有的屬性名重複,任何人實現的編譯器都可以寫入自己定義的屬性資訊,Java虛擬機器在執行時會忽略掉它不認識的屬性。
我總結了一些比較常見的屬性,如下表:
屬性名稱 | 使用位置 | 含義 |
---|---|---|
Code | 方法表 | Java程式碼編譯成的位元組碼指令 |
ConstantValue | 欄位表 | final關鍵字定義的常量值 |
Exceptions | 方法表 | 方法丟擲的異常 |
InnerClasses | 類檔案 | 內部類列表 |
LineNumberTable | Code屬性 | Java原始碼的行號與位元組碼指定的對應關係 |
LocalVariableTable | Code屬性 | 方法的區域性變數描述 |
SourceFile | 類檔案 | 記錄原始檔名稱 |
對於每個屬性,它的名稱都從常量池中引用一個CONSTANT_Utf8_info型別的常量,而屬性值的結構則是完全自定義的,只需要用一個u4型別來說明屬性值所佔的位數就可以了。具體結構如下:
型別 | 名稱 | 數量 | 含義 |
---|---|---|---|
u2 | attribute_name_index | 1 | 屬性名稱索引 |
u2 | attribute_length | 1 | 屬性值所佔的位數 |
u1 | info | attribute_length | 屬性值 |
歡迎關注微信公眾號:萬貓學社,每週一分享Java技術乾貨。
總結
Class檔案主要由魔數、次版本號、主版本號、常量池集合、訪問標誌、類索引、父類索引、介面索引集合、欄位表集合、方法表集合和屬性表集合組成。隨著JDK版本的不斷升級,Class檔案結構也在不斷更新,學習之路,永不止步。
歡迎關注微信公眾號:萬貓學社,每週一分享Java技術乾貨