Class 類文件是一組以 8 字節爲基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在 Class 文件中,中間沒有添加任何分隔符。當遇到須要佔用 8 字節以上空間的數據項目時,則按照高位在前(最高位字節在地址最低位)的方式分割成若干個 8 位字節進行存儲。數組
Class 文件格式採用一種相似 C 語言結構體的僞結構來存儲數據,這種僞結構中只有兩種數據類型:無符號數和表。安全
不管是無符號數仍是表,當須要描述同一類型但數量不定的多個數據時,會使用一個前置的容量計數器加若干個連續數據項的形式,這若干個連續數據項稱爲集合。架構
Class 文件格式:ide
類型 | 名稱 | 數量 |
---|---|---|
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 文件的頭 4 個字節稱爲魔數,用於肯定該文件是否爲一個能被虛擬機接受的 Class 文件。其值爲:0xCAFEBABE(咖啡寶貝?)。this
緊接着魔數的 4 個字節存儲的是 Class 文件的版本號:第 五、6 個字節是次版本號,第 七、8 個字節是主版本號。編碼
緊接着主次版本號以後的是常量池入口,常量池能夠理解爲 Class 文件中的資源倉庫。線程
因爲常量池中常量的數量是不固定的,因此在常量池入口放置了一個 u2 類型的常量池容量計數器。該計數器的索引值是從 1 而不是從 0 開始,當表示「不引用任何一個常量池項目」時,則可將計數器置爲 0。code
常量池主要存放兩大類常量:字面量和符號引用。每一項常量都是一個表,這些表開始的第一位是一個 u1 類型的標誌位,表明當前常量所屬的常量類型。常量池目前有 14 種常量類型,它們各自均有本身的結構。對象
常量池的項目類型:繼承
類型 | 標誌 | 描述 |
---|---|---|
CONSTANCT_Utf8_info | 1 | UTF-8 編碼的字符串 |
CONSTANCT_Integer_info | 3 | 整型字面量 |
CONSTANCT_Float_info | 4 | 浮點型字面量 |
CONSTANCT_Long_info | 5 | 長整型字面量 |
CONSTANCT_Double_info | 6 | 雙精度浮點型字面量 |
CONSTANCT_Class_info | 7 | 類或接口的符號引用 |
CONSTANCT_String_info | 8 | 字符串類型字面量 |
CONSTANCT_Fieldref_info | 9 | 字段的符號引用 |
CONSTANCT_Methodref_info | 10 | 類中方法的符號引用 |
CONSTANCT_InterfaceMethodref_info | 11 | 接口中方法的符號引用 |
CONSTANCT_NameAndType_info | 12 | 字段或方法的部分符號引用 |
CONSTANCT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANCT_MethodType_info | 16 | 標識方法類型 |
CONSTANCT_InvokeDynamic_info | 18 | 表示一個動態方法調用點 |
常量類型結構:
(1)CONSTANT_Class_info 類型常量
類型 | 名稱 | 數量 | 描述 |
---|---|---|---|
u1 | tag | 1 | 標誌位,值爲 0x07 |
u2 | name_index | 1 | 索引值,指向常量池中一個 CONSTANT_Utf8_info 類型常量,表示這個類(或接口)的全限定名 |
(2)CONSTANT_Utf8_info 類型常量
類型 | 名稱 | 數量 | 描述 |
---|---|---|---|
u1 | tag | 1 | 標誌位,值爲 0x01 |
u2 | length | 1 | UTF-8 編碼的字符串佔用的字節數 |
u1 | bytes | length | 長度爲 length 的 UTF-8 編碼的字符串 |
(3)...
常量池以後,緊接着的兩個字節表明訪問標誌,用於識別一些類或接口層次的訪問信息,包括:這個 Class 是類仍是接口、是否認義爲 public 類型、是否認義爲 abstract 類型、是否被聲明爲 final(只有類可設置)等。
訪問標誌:
標誌名稱 | 標誌值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否爲 public 類型 |
ACC_FINAL | 0x0010 | 是否被聲明爲 final,只有類可設置 |
ACC_SUPER | 0x0020 | 是否容許使用 invokespecial 字節碼指令的新語意,invokespecial 指令的語意在 JDK1.0.2 發生過改變,爲了區別使用哪一種語意,JDK1.0.2 以後編譯出來的類的這個標誌都必須爲真 |
ACC_INTERFACE | 0x0200 | 標識這個一個接口 |
ACC_ABSTRACT | 0x0400 | 是否爲 abstract 類型 |
ACC_SYNTHETIC | 0x1000 | 標識這個類並不是由用戶代碼產生 |
ACC_ANNOTATION | 0x2000 | 標識這是一個註解 |
ACC_ENUM | 0x4000 | 標識這是一個枚舉 |
類索引和父類索引都是 u2 類型的數據,而接口索引集合是一組 u2 類型的數據的集合,Class 文件由這三項數據肯定這個類的繼承關係。
字段表用於描述接口或類中聲明的變量。字段包括類級變量以及實例變量,但不包括在方法內部聲明的局部變量。
字段表結構:
類型 | 名稱 | 數量 |
---|---|---|
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_VOLATILE | 0x0040 | 字段是否 volatile |
ACC_TRANSIENT | 0x0080 | 字段是否 transient |
ACC_SYNTHETIC | 0x1000 | 字段是否由編譯器自動產生的 |
ACC_ENUM | 0x4000 | 字段是否 enum |
方法表的結構與字段表同樣,依次包括了訪問標誌(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 | 方法是否 stricftp |
ACC_SYNTHETIC | 0x1000 | 方法是否由編譯器自動產生的 |
方法裏的 Java 代碼,通過編譯器編譯成字節碼指令後,存放在方法屬性表集合中一個名爲「Code」的屬性裏面。
在 Class 文件、字段表、方法表均可以攜帶本身的屬性表集合,以用於描述某些場景專有的信息。
屬性表不要求各個屬性表具備嚴格的順序,而且只要不與已有屬性名重複,任何人實現的編譯器均可以向屬性表中寫入本身定義的屬性信息,Java 虛擬機運行時會忽略掉它不認識的屬性。
屬性表結構:
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |
(1)Code 屬性
Java 程序方法體中的代碼通過 Javac 編譯器處理後,最終變成字節碼指令存儲在 Code 屬性內。
Code 屬性表的結構:
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | max_stack | 1 |
u2 | max_locals | 1 |
u4 | code_length | 1 |
u1 | code | code_length |
u2 | exception_table_length | 1 |
exception_info | exception_table | exception_table_length |
u2 | attritutes_count | 1 |
attribute_info | attritutes | attritutes_count |
(2)Exceptions 屬性
用於列舉出方法中可能拋出的受查異常,也就是方法描述時在 throws 關鍵字後列舉的異常。
Exceptions 屬性表的結構:
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | number_of_exceptions | 1 |
u2 | exception_index_table | number_of_exceptions |
(3)...
Java 虛擬機的指令由一個操做碼和零至多個操做數構成。因爲 Java 虛擬機採用面向操做數棧而不是寄存器的架構,全部大多數指令都不包括操做數,只有一個操做碼。可是大多數指令都包含了其操做所對應的數據類型信息。
若是不考慮異常處理,Java 虛擬機的解釋器可使用下面的僞代碼看成最基本的執行模型來理解:
do { 自動計算 PC 寄存器的值加 1; 根據 PC 寄存器的指示位置,從字節碼流中取出操做碼; if ( 字節碼存在操做數 ) 從字節碼流中取出操做數; 執行操做碼所定義的操做; }
對於大多數與數據類型相關的字節碼指令,它們的操做碼助記符中都有特殊的字符來表示專門爲哪一種數據類型服務:i 表明對 int 類型的數據操做,l 表明 long,s 表明 short,b 表明 byte,c 表明 char,f 表明 float,d 表明 double,a 表明 reference。
加載和存儲指令用於將數據在棧幀中的局部變量表和操做數棧之間來回傳輸,這類指令包括:
以上列舉的指令助記符中,有一部分是以尖括號結尾的指令。這幾組指令是帶有一個操做數的通用指令(如 iload)的特殊形式,它們省略了顯式的操做數,而是將操做數隱含在指令中。例如:iload_0 表明操做數爲 0 的 iload 指令。
運算或算術指令用於對兩個操做數以上的值進行某種特定運算,並把結果從新存入到操做數棧頂。大致上算術指令可分爲兩種:對整型數據進行運算的指令和對浮點型數據進行運算的指令。全部的算術指令以下:
類型轉換指令能夠將兩種不一樣的數值類型進行相互轉換,這些轉換操做通常用於實現用戶代碼中的顯式類型轉換操做,或者用來處理字節碼指令集中數據類型相關指令沒法與數據類型一一對應的問題。
Java 虛擬機直接支持(即轉換時無需顯示的轉換指令)如下數值類型的寬化類型轉換(小範圍類型向大範圍類型的安全轉換):
相對的,處理窄化類型轉換時,必須顯示地使用轉換指令來完成,這些指令包括:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l、d2f。窄化類型轉換可能會致使轉換結果產生不一樣的正負號、不一樣的數量級的狀況,轉換過程極可能會致使數值的精度丟失。
雖然類實例和數組都是對象,但 Java 虛擬機對類實例和數組的建立與操做使用了不一樣的字節碼指令。相關指令以下:
Java 虛擬機提供了一些用於直接操做操做數棧的指令,包括:
控制轉移指令可讓 Java 虛擬機有條件或無條件地從指定位置的指令繼續執行程序,而不是從控制轉移指令的下一條指令繼續執行程序。從概念模型上理解,可認爲控制轉移指令就是在有條件或無條件地修改 PC 寄存器的值。控制轉移指令以下:
方法調用指令與數據類型無關,包括:
方法返回指令是根據返回值的類型區分的,包括:ireturn(用於返回值是 boolean、byte、char、short、int 的方法)、lreturn、freturn、dreturn、areturn、return(用於 void 方法、實例初始化方法、類和接口的類初始化方法)。
Java 虛擬機中顯式拋出異常的操做(throw 語句)都由 athrow 指令實現。而處理異常(catch 語句)則不是由字節碼指令來實現的(好久以前曾經使用 jsr 和 ret 指令實現),而是採用異常表來完成。
Java 虛擬機能夠支持方法級的同步和方法內部一段指令序列的同步,這兩種同步結構都是使用管程(Monitor)來支持的。
方法級的同步是隱式的,即無須經過字節碼指令來控制。虛擬機能夠從方法訪問標誌 ACC_SYNCHRONIZED 得知一個方法是否聲明爲同步方法。若是方法訪問標誌 ACC_SYNCHRONIZED 被設置爲 true,執行線程就要求先成功持有管程,而後才能執行方法,最後當方法完成時釋放管程。
同步一段指令集序列一般是由 synchronized 語句來表示的,Java 虛擬機的指令集中由 monitorenter 和 monitorexit 兩條指令來支持 synchronized 關鍵字語義。