Class 類檔案結構
阿新 • • 發佈:2021-01-06
> 本文部分摘自《深入理解 Java 虛擬機器第三版》
## 概述 我們知道,Java 具有跨平臺性,其實現基礎就是虛擬機器和位元組碼儲存格式。Java 虛擬機器不與 Java 語言繫結,只與 Class 檔案所關聯。Java 虛擬機器作為一個通用的、與機器無關的執行平臺,任何語言都可以將 Java 虛擬機器作為它們的執行基礎,以 Class 檔案作為它們產品的交付媒介。 Class 檔案是一組以 8 個位元組為基礎單位的二進位制流,各個資料專案嚴格按照順序緊湊地排列在檔案之中,中間沒有新增任何分隔符,這使得整個 Class 檔案中儲存的內容幾乎全部是程式執行的必要資料。當遇到需佔用 8 個位元組以上空間的資料項時,則會按照高位在前的方式分割成若干個 8 個位元組進行儲存。 Class 檔案中有兩種資料型別,分別是無符合數和表: - 無符號數屬於基本資料型別,以 u1、u2、u4、u8 來分別代表 1 個位元組、2 個位元組、4 個位元組和 8 個位元組的無符號數,無符號數可以用來描述數字、索引引用、數量值或 UTF-8 編碼構成字串值 - 表是由多個無符號數或其他表作為資料項構成的複合資料型別,一般以 _info 結尾。表用於描述有層次關係的複合結構的資料,整個 Class 檔案本質上也可以視作是一張表 無論是無符號數還是表,當需要描述同一型別但數量不定的多個數據時,經常會使用一個前置的容量計數器加若干個連續的資料項的形式,這時候稱這一系列連續的某一型別的資料為某一型別的集合。 下面是 Class 檔案格式:
型別 | 名稱 | 數量 |
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 | attribute_count | 1 |
attribute_info | attributes | attributes_count |
## 魔數和 Class 檔案版本 Class 檔案的頭 4 個位元組被稱為魔數(Magic Number),它的唯一作用是確定該 Class 檔案是否能被虛擬機器接受,其值為 0xCAFEBABE(咖啡寶貝)。 緊接著魔數的 4 個位元組儲存的是 Class 檔案的版本號:第 5 和第 6 個位元組是次版本號(Minor Version),第 7 和第 8 個位元組是主版本號(Major Version)。Java 版本號從 45 開始,以後每個 JDK 大版本釋出則主版本號加 1。高版本的 JDK 能向下相容以前版本的 Class 檔案,但不能執行以後版本的 Class 檔案。
型別 | 標誌 | 描述 |
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 | 表示一個模組中開放或者匯出的包 |
## 訪問標誌 常量池結束之後,緊接著的 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 | 標誌這是一個列舉 |
## 類索引、父類索引與介面索引集合 類索引(this_class)、父類索引(super_class)和介面索引(interfaces)都按順序排列在訪問標誌之後,類索引和父類索引用兩個 u2 型別的索引值表示,而介面索引是一組 u2 型別的資料的集合。 類索引用於確定該類的全限定名,父類索引確定該類的父類的全限定名,由於 Java 不允許多繼承,因此父類索引只有一個,Object 類的父類索引為 0。類索引和父類索引各自指向一個 CONSTANT_Class_info 的類描述符常量,通過這個索引值可以找到定義在 CONSTANT_Utf8_info 型別的常量中的全限定名字串。 對於介面索引集合,入口的第一項 u2 型別的資料為介面計數器(interfaces_count),表示索引表的容量,如果該類沒有實現任何介面,則該計數器的值為 0,後面介面的索引表不再佔用任何位元組。
## 欄位表集合方法表集合 欄位表集合(field_info)用於描述介面或類中宣告的變數,包括類級變數以及例項級變數,但不包括在方法內部宣告的區域性變數。欄位包含待資訊有欄位的作用域(public、private、protected)、是例項變數還是類變數(static)、可變性(final)等等。這些資訊要麼有,要麼沒有,很適合用標誌位來表示,而欄位叫什麼,被定義為什麼資料型別,這些都無法固定,只能用常量池中的常量來描述。 欄位表結構如下: | 型別 | 名稱 | 數量 | | -------------- | ---------------- | ---------------- | | u2 | access_flags | 1 | | u2 | name_index | 1 | | u2 | descriptor_index | 1 | | u2 | attributes_count | 1 | | attribute_info | attributes | attributes_count | access_flags 用來標識欄位修飾符(public、static、final、volatile ...),name_index 和 descriptor_index 都是對常量池的引用,分別代表欄位的簡單名稱以及欄位和方法的描述符。之後的是屬性表集合,用於儲存一些額外資訊。 欄位表集合不會列出從父類或父介面繼承而來的欄位,但有可能出現原本 Java 程式碼中沒有的欄位,例如內部類為了保持對外部類的訪問性,編譯器會自動新增指向外部類例項的欄位。 方法表集合與欄位表集合幾乎完全一致,僅在標誌和屬性表集合的可選項中有所區別。至於方法裡面的程式碼,則經編譯後存放在方法屬性表集合中一個名為 Code 的屬性裡面