Class檔案結構&位元組碼指令

微笑面對生活發表於2018-07-27

class檔案結構

Class檔案儲存的內容稱為位元組碼(ByteCode),包含了JVM指令集和符號表以及若干其他輔助資訊。

class檔案是一組以8位位元組為基礎單位的二進位制流,各個資料專案嚴格按照順序緊湊的排列在Class檔案中,中間沒有新增任何分隔符,整個Class檔案中儲存的內容幾乎全部是程式執行的必要的資料,沒有空隙存在。

當遇到8位位元組以上的空間的資料項時,則會按照高位在前的方式分割成若干個8位位元組進行儲存。

Class檔案中有兩種資料型別,分別是無符號數和表。

無符號數

無符號數屬於基本的資料型別,以u1、u2、u4、u8來表示一個位元組、兩個位元組...的無符號數;無符號數用來描述數字、索引引用、數量值或UTF-8編碼構成的字串值。

表是由多個無符號數或其他表作為資料項構成的複合資料型別,一般以"_info"結尾,用來描述class檔案的資料結構。

特點:節省儲存空間,提高處理效能

ClassFile { 
    u4 magic; 
    u2 minor_version; 
    u2 major_version; 
    u2 constant_pool_count; 
    cp_info constant_pool[constant_pool_count-1]; 
    u2 access_flags; 
    u2 this_class; 
    u2 super_class; 
    u2 interfaces_count; 
    u2 interfaces[interfaces_count]; 
    u2 fields_count; 
    field_info fields[fields_count]; 
    u2 methods_count; 
    method_info methods[methods_count]; 
    u2 attributes_count; 
    attribute_info attributes[attributes_count]; 
}
複製程式碼
  • 魔數
  • Class檔案版本
  • 常量池
  • 訪問標誌
  • 類索引,父類索引,介面索引集合
  • 欄位表集合
  • 方法表集合
  • 屬性表集合

u2表示無符號數2個位元組 u4表示無符號數4個位元組

Class檔案設計理念和意義

1. 魔數magic

魔數的唯一作用是確定這個檔案是否為一個能被虛擬機器所接受的Class檔案。魔數值固定為0xCAFEBABE,不會改變。

證明magic作用

建立一個class檔案 magic.class ,內容是magic test,直接執行java magic操作:

84407@FantJ MINGW64 ~/Desktop
$ java magictest
▒▒▒▒: ▒▒▒▒▒▒▒▒ magictest ʱ▒▒▒▒ LinkageError
        java.lang.ClassFormatError: Incompatible magic value 1886741100 in class file magictest

複製程式碼

報錯意思是:magic矛盾,然後給了個magic value的十進位制數,那麼可以識別的magic十進位制應該是多少呢。

Class檔案結構&位元組碼指令
應該是3405691582

那麼,然後我用javac編譯的正常java檔案生成class檔案,用binary viewer 檢視:

Class檔案結構&位元組碼指令

minor_version、major_version

魔數往後後面四位:表示位元組碼版本,分別表示Class檔案的副、主版本。當今用的最廣的幾個版本: jdk1.8:52 jdk1.7:51 jdk1.6:50

Class檔案結構&位元組碼指令
Class檔案結構&位元組碼指令
對應版本號是52,是jdk1.8
Class檔案結構&位元組碼指令

版本向下相容

2. constant_pool_count

常量池計數器,值等於constant_pool表中的成員數加1,佔用兩個位元組

3. constant_pool[]常量池

Java虛擬機器指令執行時依賴常量池(constant_pool)表中的符號資訊。

所有的常量池項都具有如下通用格式:

cp_info {
    u1 tag; 
    u1 info[]; 
}
複製程式碼

info[]項的內容tag由的型別所決定。tag有效的型別和對應的取值在下表列出

常量型別
CONSTANT_Class 7
CONSTANT_Fieldref 9
CONSTANT_Methodref 10
CONSTANT_InterfaceMethodref 11
CONSTANT_String 8
CONSTANT_Integer 3
CONSTANT_Float 4
CONSTANT_Long 5
CONSTANT_Double 6
CONSTANT_NameAndType 12
CONSTANT_Utf8 1
CONSTANT_MethodHandle 15
CONSTANT_MethodType 16
CONSTANT_InvokeDynamic 18
3.1 CONSTANT_Class_info結構

表示類或介面

CONSTANT_Class_info {
    u1 tag; 
    u2 name_index;
}
複製程式碼

name_index必須是對常量池的一個有效索引

3.2 CONSTANT_Fieldref_info, CONSTANT_Methodref_info和CONSTANT_InterfaceMethodref_info結構

欄位:

CONSTANT_Fieldref_info {
    u1 tag; 
    u2 class_index; 
    u2 name_and_type_index; 
}
複製程式碼

方法:

CONSTANT_Methodref_info { 
    u1 tag; 
    u2 class_index; 
    u2 name_and_type_index; 
}
複製程式碼

介面方法:

CONSTANT_InterfaceMethodref_info {
    u1 tag; 
    u2 class_index; 
    u2 name_and_type_index; 
}
複製程式碼

class_index必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Class_info結構,表示一個類或介面,當前欄位或方法是這個類或介面的成員。

CONSTANT_Methodref_info結構的class_index項的型別必須是類(不能是介面)。CONSTANT_InterfaceMethodref_info結構的class_index項的型別必須是介面(不能是類)。CONSTANT_Fieldref_info結構的class_index項的型別既可以是類也可以是介面。

name_and_type_index必須是對常量池的有效索引,表示當前欄位或方法的名字和描述符。 在一個CONSTANT_Fieldref_info結構中,給定的描述符必須是欄位描述符。而CONSTANT_Methodref_infoCONSTANT_InterfaceMethodref_info中給定的描述符必須是方法描述符。

3.3 CONSTANT_String_info結構

用來表示String的結構

CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}
複製程式碼

string_index必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info 結構,表示一組Unicode碼點序列,這組Unicode碼點序列最終會被初始化為一個String物件。

3.4CONSTANT_Integer_info和CONSTANT_Float_info結構

表示4位元組(int和float)的數值常量:

CONSTANT_Integer_info {
    u1 tag; 
    u4 bytes; 
} 
CONSTANT_Float_info { 
    u1 tag; 
    u4 bytes;
}
複製程式碼
3.5CONSTANT_Long_info和CONSTANT_Double_info結構

表示8位元組(long和double)的數值常量

CONSTANT_Long_info {
    u1 tag; 
    u4 high_bytes; 
    u4 low_bytes; 
} 

CONSTANT_Double_info { 
    u1 tag; 
    u4 high_bytes; 
    u4 low_bytes; 
}
複製程式碼
3.6 CONSTANT_NameAndType_info結構

表示欄位或方法,但是和前面介紹的3個結構不同,CONSTANT_NameAndType_info結構沒有標識出它所屬的類或介面

CONSTANT_NameAndType_info { 
    u1 tag; 
    u2 name_index; 
    u2 descriptor_index;
}
複製程式碼

name_index項的值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info結構,這個結構要麼表示特殊的方法名,要麼表示一個有效的欄位或方法的非限定名(Unqualified Name)。

descriptor_index項的值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info結構,這個結構表示一個有效的欄位描述符或方法描述符。

3.7 CONSTANT_Utf8_info結構

用於表示字串常量的值

CONSTANT_Utf8_info {
    u1 tag; 
    u2 length; 
    u1 bytes[length]; 
}
複製程式碼

CONSTANT_Utf8_info結構中的內容是以length屬性確定長度的

3.8 CONSTANT_MethodHandle_info結構

表示方法控制程式碼

CONSTANT_MethodHandle_info {
    u1 tag;
    u1 reference_kind;
    u2 reference_index;
}
複製程式碼

reference_kind項的值必須在1至9之間(包括1和9),它決定了方法控制程式碼的型別。

  1. 如果reference_kind項的值為1(REF_getField)、2(REF_getStatic)、3(REF_putField)或4(REF_putStatic),那麼常量池在reference_index索引處的項必須是CONSTANT_Fieldref_info結構,表示由一個欄位建立的方法控制程式碼。
  2. 如果reference_kind項的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或8(REF_newInvokeSpecial),那麼常量池在reference_index索引處的項必須是CONSTANT_Methodref_info結構,表示由類的方法或建構函式建立的方法控制程式碼。
  3. 如果reference_kind項的值是9(REF_invokeInterface),那麼常量池在reference_index索引處的項必須是CONSTANT_InterfaceMethodref_info結構,表示由介面方法建立的方法控制程式碼。
  4. 如果reference_kind項的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或9(REF_invokeInterface),那麼方法控制程式碼對應的方法不能為例項初始化()方法或類初始化方法()。
  5. 如果reference_kind項的值是8(REF_newInvokeSpecial),那麼方法控制程式碼對應的方法必須為例項初始化()方法。
3.9 CONSTANT_MethodType_info結構

表示方法型別

CONSTANT_MethodType_info { 
    u1 tag; 
    u2 descriptor_index; 
}
複製程式碼
3.10 CONSTANT_InvokeDynamic_info結構

表示invokedynamic指令所使用到的引導方法(Bootstrap Method)、引導方法使用到動態呼叫名稱(Dynamic Invocation Name)、引數和請求返回型別、以及可以選擇性的附加被稱為靜態引數(Static Arguments)的常量序列。

CONSTANT_InvokeDynamic_info { 
    u1 tag; 
    u2 bootstrap_method_attr_index; 
    u2 name_and_type_index; 
}
複製程式碼

bootstrap_method_attr_index項的值必須是對當前Class檔案中引導方法表的bootstrap_methods[]陣列的有效索引。

name_and_type_index項的值必須是對當前常量池的有效索引,常量池在該索引處的項必須是CONSTANT_NameAndType_info結構,表示方法名和方法描述符。

4. access_flags:訪問標誌

訪問標誌,access_flags是一種掩碼標誌,用於表示某個類或者介面的訪問許可權及基礎屬性。access_flags的取值範圍和相應含義見下表。

標記名 含義
ACC_PUBLIC 0x0001 可以被包的類外訪問。
ACC_FINAL 0x0010 不允許有子類。
ACC_SUPER 0x0020 當用到invokespecial指令時,需要特殊處理的父類方法。
ACC_INTERFACE 0x0200 標識定義的是介面而不是類。
ACC_ABSTRACT 0x0400 不能被例項化。
ACC_SYNTHETIC 0x1000 標識並非Java原始碼生成的程式碼。
ACC_ANNOTATION 0x2000 標識註解型別
ACC_ENUM 0x4000 標識列舉型別

5. this_class:類索引

this_class的值必須是對constant_pool表中專案的一個有效索引值。

是一個對constant_pool表中專案的一個有效索引值,表示指向常量池的第幾個位置。

6. super_class:父類索引

表示這個Class檔案所定義的類的直接父類,如果Class檔案的super_class的值為0,那這個Class檔案只可能是定義的是java.lang.Object類,只有它是唯一沒有父類的類

是一個對constant_pool表中專案的一個有效索引值,表示指向常量池的第幾個位置。

7. interfaces_count:介面計數器

表示有這個類有幾個介面。

8. interfaces[]:介面表

成員所表示的介面順序和對應的原始碼中給定的介面順序(從左至右)一樣,即interfaces[0]對應的是原始碼中最左邊的介面。

是一個對constant_pool表中專案的一個有效索引值,表示指向常量池的第幾個位置。

表示當前類或介面的直接父介面數量

9. fields_count:欄位計數器

表示當前Class檔案fields[]陣列的成員個數

10. fields[]:欄位表

每個成員都必須是一個fields_info結構的資料項,描述當前類或介面宣告的所有欄位,但不包括從父類或父介面繼承的部分。

用於表示當前類或介面中某個欄位的完整描述

field_info {
    u2 access_flags; 
    u2 name_index;      //對常量池的一個有效索引
    u2 descriptor_index;     //對常量池的一個有效索引
    u2 attributes_count;     //當前欄位的附加屬性的數量
    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,表示欄位為列舉型別。

attributes表的每一個成員的值必須是attribute結構,一個欄位可以有任意個關聯屬性。

11. methods_count:方法計數器

methods_count的值表示當前Class檔案methods[]陣列的成員個數,Methods[]陣列中每一項都是一個method_info結構的資料項。

12. methods[]:方法表

method_info結構可以表示類和介面中定義的所有方法,包括例項方法、類方法、例項初始化方法方法和類或介面初始化方法方法。methods[]陣列只描述當前類或介面中宣告的方法,不包括從父類或父介面繼承的方法。

methods[]陣列中的每個成員都必須是一個method_info結構的資料項,用於表示當前類或介面中某個方法的完整描述。

method_info { 
    u2 access_flags; 
    u2 name_index; 
    u2 descriptor_index; 
    u2 attributes_count; 
    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_SYNCHRONIZED 0x0020 synchronized,方法由管程同步
ACC_BRIDGE 0x0040 bridge,方法由編譯器產生
ACC_VARARGS 0x0080 表示方法帶有變長引數
ACC_NATIVE 0x0100 native,方法引用非java語言的本地方法
ACC_ABSTRACT 0x0400 abstract,方法沒有具體實現
ACC_STRICT 0x0800 strictfp,方法使用FP-strict浮點格式
ACC_SYNTHETIC 0x1000 方法在原始檔中不出現,由編譯器產生

name_indexdescriptor_index 兩屬性是對常量池的一個有效索引 attributes_count的項的值表示這個方法的附加屬性的數量。 attributes 表的每一個成員的值必須是attribute結構,一個方法可以有任意個與之相關的屬性。

13. attributes_count:屬性計數器

attributes表中每一項都是一個attribute_info結構的資料項。

attributes_count的值表示當前Class檔案attributes表的成員個數。

14. attributes[]:屬性表

attributes表的每個項的值必須是attribute_info結構,在Class檔案格式中的ClassFile結構、field_info結構,method_info結構和Code_attribute結構都有使用,所有屬性的通用格式如下:

attribute_info {
    u2 attribute_name_index; 
    u4 attribute_length; 
    u1 info[attribute_length];
}
複製程式碼

attribute_name_index必須是對當前Class檔案的常量池的有效16位無符號索引。表示當前屬性的名字。

attribute_length項的值給出了跟隨其後的位元組的長度,這個長度不包括attribute_name_indexattribute_name_index項的6個位元組。

14.1 ConstantValue屬性

ConstantValue屬性是定長屬性,位於field_info結構的屬性表中。如果該欄位為靜態型別(即field_info結構的access_flags項設定了ACC_STATIC標誌),則說明這個field_info結構表示的常量欄位值將被分配為它的ConstantValue屬性表示的值,這個過程也是類或介面申明的常量欄位(Constant Field)初始化的一部分。這個過程發生在引用類或介面的類初始化方法執行之前。

ConstantValue_attribute { 
    u2 attribute_name_index; 
    u4 attribute_length; 
    u2 constantvalue_index; 
}
複製程式碼

attribute_name_index項的值,必須是一個對常量池的有效索引。 attribute_length項的值固定為2。 constantvalue_index項的值,必須是一個對常量池的有效索引。

14.2 Code屬性

Code屬性是一個變長屬性,位於method_info結構的屬性表。一個Code屬性只為唯一一個方法、例項類初始化方法或類初始化方法儲存Java虛擬機器指令及相關輔助資訊。所有Java虛擬機器實現都必須能夠識別Code屬性。如果方法被宣告為native或者abstract型別,那麼對應的method_info結構不能有明確的Code屬性,其它情況下,method_info有必須有明確的Code屬性。

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length; 
    u2 max_stack;
    u2 max_locals;
    u4 code_length; 
    u1 code[code_length]; 
    u2 exception_table_length; 
    {   u2 start_pc;
        u2 end_pc; 
        u2 handler_pc; 
        u2 catch_type; 
    } exception_table[exception_table_length]; 
    u2 attributes_count; 
    attribute_info attributes[attributes_count];
}
複製程式碼

attribute_name_index項的值必須是對常量池的有效索引 attribute_length項的值表示當前屬性的長度,不包括開始的6個位元組。 max_stack項的值給出了當前方法的運算元棧在執行執行的任何時間點的最大深度。 max_locals項的值給出了分配在當前方法引用的區域性變數表中的區域性變數個數,包括呼叫此方法時用於傳遞引數的區域性變數。long和double型的區域性變數的最大索引是max_locals-2,其它型別的區域性變數的最大索引是max_locals-1. code_length項給出了當前方法的code[]陣列的位元組數,code_length的值必須大於0,即code[]陣列不能為空。 code[]陣列給出了實現當前方法的Java虛擬機器位元組碼。 exception_table_length項的值給出了exception_table[]陣列的成員個數量。 exception_table[]陣列的每個成員表示code[]陣列中的一個異常處理器(Exception Handler)。exception_table[]陣列中,異常處理器順序是有意義的(不能隨意更改)。 start_pcend_pc兩項的值表明了異常處理器在code[]陣列中的有效範圍。 handler_pc項表示一個異常處理器的起點 如果catch_type項的值不為0,那麼它必須是對常量池的一個有效索引 attributes_count項的值給出了Code屬性中attributes表的成員個數。 屬性表的每個成員的值必須是attribute結構。一個Code屬性可以有任意數量的可選屬性與之關聯。

14.3 StackMapTable屬性

StackMapTable屬性是一個變長屬性,位於Code屬性的屬性表中。這個屬性會在虛擬機器類載入的型別階段被使用。

StackMapTable_attribute { 
    u2 attribute_name_index;
    u4 attribute_length; 
    u2 number_of_entries; 
    stack_map_frame entries[number_of_entries];
}
複製程式碼

attribute_name_index項的值必須是對常量池的有效索引 attribute_length項的值表示當前屬性的長度,不包括開始的6個位元組。 number_of_entries項的值給出了entries表中的成員數量。Entries表的每個成員是都是一個stack_map_frame結構的項。 entries表給出了當前方法所需的stack_map_frame結構。

...更多的屬性就不在這一一貼了,太多了,需要的時候查官方文件即可:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4

位元組碼指令

java虛擬機器指令由一個位元組長度的,代表某種特定操作含義的數字(稱之為操作碼),以及隨後的代表此操作所需引數的運算元而構成。

操作碼的長度為1個位元組,所以最大隻有256條

常量入棧指令

Class檔案結構&位元組碼指令

區域性變數值轉載到棧中指令

Class檔案結構&位元組碼指令

將棧頂值儲存到區域性變數中指令

Class檔案結構&位元組碼指令

wide指令

Class檔案結構&位元組碼指令

通用(無型別)棧操作指令

Class檔案結構&位元組碼指令

型別轉換指令

Class檔案結構&位元組碼指令

整數運算

Class檔案結構&位元組碼指令

浮點運算

Class檔案結構&位元組碼指令

邏輯運算——移位運算

Class檔案結構&位元組碼指令

邏輯運算——按位布林運算

Class檔案結構&位元組碼指令

控制流指令——條件跳轉指令

Class檔案結構&位元組碼指令

控制流指令——比較指令

Class檔案結構&位元組碼指令

控制流指令——無條件跳轉指令

Class檔案結構&位元組碼指令

控制流指令——表跳轉指令

Class檔案結構&位元組碼指令

控制流指令——異常和finally

Class檔案結構&位元組碼指令

物件操作指令

Class檔案結構&位元組碼指令

陣列操作指令

Class檔案結構&位元組碼指令

方法呼叫指令

Class檔案結構&位元組碼指令

方法返回指令

Class檔案結構&位元組碼指令

執行緒同步指令

Class檔案結構&位元組碼指令

指令參考:https://blog.csdn.net/web_code/article/details/12164733

一個簡單的demo分析

Test.java

public class Test {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        int c = a+b;
        System.out.println(c);
    }
}
複製程式碼

javap -v Test.class

   #2 = Fieldref           #24.#25        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #26.#27        // java/io/PrintStream.println:(I)V
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        10    //把10擴充套件成int入棧
         2: istore_1      //將棧頂int型別值儲存到區域性變數1中
         3: bipush        20     //把20擴充套件成int入棧
         5: istore_2     //將棧頂int型別值儲存到區域性變數2中
         6: iload_1      //從區域性變數1中裝載int型別值入棧  
         7: iload_2     //從區域性變數2中裝載int型別值入棧  
         8: iadd       // 將棧頂兩int型別數相加,結果入棧。
         9: istore_3     //將棧頂int型別值儲存到區域性變數3中
        10: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;獲取靜態欄位的值。#2表示常量池的索引
        13: iload_3
        14: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V 執行時方法繫結呼叫方法。
        17: return      //void函式返回。
複製程式碼

相關文章