1. 程式人生 > 程式設計 >class位元組碼,這次我算看透你了!

class位元組碼,這次我算看透你了!

簡介

java程式碼是通過java編譯器編譯成class檔案,然後由jvm載入執行的,jvm遮蔽了底層平臺系統執行細節,所以可以做到Compile Once,Run Anywhere。

編譯後的class檔案,是一個二進位制流檔案,例如下面的類:

public class ServiceResult<T> {

    private static final int SUCCESS_CODE = 200;
    private static final String SUCCESS_MESSAGE = "Success";

    private int code;
    private
String message; private T data; public ServiceResult(T data) { this.code = SUCCESS_CODE; this.message = SUCCESS_MESSAGE; this.data = data; } public ServiceResult(int code,String message,T data) { this.code = code; this.message = message; this
.data = data; } public boolean isSuccess() { return code == SUCCESS_CODE; } public int getCode() { return code; } public String getMessage() { return message; } public T getData() { return data; } @Override public String toString
()
{ final StringBuilder sb = new StringBuilder("ServiceResult{"); sb.append("code=").append(code); sb.append(",message='").append(message).append('\''); sb.append(",data=").append(data); sb.append('}'); return sb.toString(); } } 複製程式碼

編譯後得到的class,以16進位制格式開啟如下:

注:class檔案以位元組(8位元)為單位,用u1,u2,u4,u8分別表示1個位元組,2個位元組,4個位元組,8個位元組的無符號數,採用Big-edian形式,即高位位元組在前。

Class結構

二進位制class檔案如果用類c語言結構體的形式來描述其邏輯結構,則如下圖所示:

從圖中可知,class檔案主要包含magic,minor version,major version,constant pool,access flags,this_class,super class,interfaces,fields,methods,attributes 11個部分,每個部分之間緊湊的拼接在一起,沒有分界符分割,下面分別介紹每個結構。

在開始介紹各個結構之前,需要說明本文以jvm1.8為準

本文有些長,這裡排版看起來更舒服些

1. magic

魔數: 佔4個位元組的無符號數,固定為0xCAFEBABE,用來標識改檔案是一個class檔案

2. minor version

次版本號: 佔兩個位元組的無符號數,範圍0~65535,與major version一起表示當前class檔案的版本,jvm可以向前相容之前的版本,但不能向後相容,即jdk7的虛擬機器器不能執行jdk8編譯的class

3. major version

主版本號: 佔兩個位元組的無符號數,jdk1.1使用的主版本號是45,以後每個大版本加1,如jdk1.8為52

4. constant pool

常量池: 常量池是class中十分重要的一部分,它可不是隻儲存著類中定義的常量而已,還儲存著class檔案中的各種元資料,包括一些字串,類名,介面名,欄位名,方法名等等……,它的作用就是被引用,常量池部分首先有兩個位元組u2記錄它包含的常量個數。

PS1:常量池就是一系列常量的陣列,它的下標是從1開始的,即有效大小是constant_pool_count-1,第0項是無效的,有些結構可以用索引0來表示沒有對常量的引用

PS2:常量池的設計有效的減小的class檔案的大小,想想那些重複使用的類名稱,字串現在只需保留一份,並且引用的地方只需要用u2儲存它在常量池中的索引就可以了

​ 因為每個常量都有一種具體的型別來代表不同的含義,光知道常量的個數還沒辦法解析出具體的常量項來,所以定義每個常量的第一個位元組u1表示該常量的型別tag,然後就可以根據該型別常量的儲存結構來解析了。

​ 常量的tag有CONSTANT_Utf8,CONSTANT_Integer,CONSTANT_Float,CONSTANT_Long,CONSTANT_Double,CONSTANT_Class,CONSTANT_String,CONSTANT_Fieldref,CONSTANT_Methodref,CONSTANT_InterfaceMethodref,CONSTANT_NameAndType,CONSTANT_MethodHandle,CONSTANT_MethodType,CONSTANT_InvokeDynamic等14種,下面對每種型別結構(型別+“_info”)作下介紹:

4.1 CONSTANT_Utf8_info

常量池中最基本的常量,用來儲存一個utf8編碼字串,如常量字串,類名,欄位名,方法名等的值都是一個對它的引用(索引)

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

tag=1,length表示字串位元組長度,如length=20,則表示接下來20個bytes是一個utf8編碼的字串。

這裡補充兩點:

  • java使用的是可變utf8編碼:ASCII 字元('\u0001' ~ '\u007F',即1~127)用1個位元組表示,null('\u0000')和 '\u0080' 到 '\u07FF'之間的字元用2個位元組表示, '\u0800' 到 '\uFFFF'之間的字元用3個位元組表示。

    逆向來看就是如果讀到一個位元組最高位是0,則是一個單位元組字元。

    讀到一個位元組最高3位是110則是一個雙位元組字元,緊接著還要再讀1個位元組。

    讀到一個位元組最高4位是1110,則是一個三位元組字元,緊接著還要再讀2個位元組。

    關於如何解碼可以檢視官方檔案,在java中,我們只需要使用new String(bytes,StandardCharset.UTF8)即可得到解碼字串

  • length使用了u2(0-65535)來表示,則其表示的字串最大長度為65535

4.2 CONSTANT_Integer_info
CONSTANT_Integer_info {
    u1 tag;
    u4 bytes;
}
複製程式碼

int,tag=3,接下來4個位元組表示該int的值。關於CONSTANT_Integer補充以下幾點:

  • big-endian,位元組高位在前,下文同理

    如果自己解析則要像下面這樣:

    int value = 0;
    byte[] data = new byte[4];
    is.read(data);
    value = (value | (((int) data[0]) & 0xff)) << Byte.SIZE * 3;
    value = (value | (((int) data[1]) & 0xff)) << Byte.SIZE * 2;
    value = (value | (((int) data[2]) & 0xff)) << Byte.SIZE;
    value = (value | (((int) data[3]) & 0xff));
    複製程式碼

    我們可以使用DataInputStream的readInt()方法讀取一個int值。

  • java中short,char,byte,boolean使用int來表示,boolean陣列則用byte陣列來表示(1個byte表示1個boolean元素)

4.3 CONSTANT_Float_info
CONSTANT_Float_info {
    u1 tag;
    u4 bytes;
}
複製程式碼

float浮點數,tag=4,接下來4個位元組表示它的值,採用 IEEE 754標準定義。可以使用DataInputStream的readFloat()方法讀取一個float值。

4.4 CONSTANT_Long_info
CONSTANT_Long_info {
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;
}
複製程式碼

tag=5,長整數,long和double在class中用兩個部分(高位4位元組,地位4位元組)儲存。可以使用DataInputStream的readLong()方法讀取一個float值。

4.5 CONSTANT_Double_info
CONSTANT_Double_info {
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;
}
複製程式碼

tag=6,雙精度浮點數,採用 IEEE 754標準定義。儲存同CONSTANT_Long一樣。

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

tag=7,表示一個類或介面,注意不是field的型別或method的引數型別、返回值型別。name_index是常量池索引,該索引處常量肯定是一個CONSTANT_Utf8_info

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

tag=8,表示一個常量字串,string_index是常量池索引,該索引處常量肯定是一個CONSTANT_Utf8_info ,儲存著該字串的值

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

tag=9,表示一個引用field資訊,包括靜態field和例項field。

class_index是常量池中一個CONSTANT_Class_info型別常量(類/介面)索引,表示field所屬類。name_and_type_index是常量池中一個CONSTANT_NameAndType_info(見下文)型別常量索引,表示field的名稱和型別。

關於field引用解釋一下,包括下面的method,介面method引用同理:

  • 以本文開頭ServiceResult類的code field為例,code在多個方法中都有用到,相比儲存多份該field資訊來講,在常量池中儲存一份該field資訊,然後在其他用到的地方儲存其索引顯然更合適。
  • CONSTANT_Fieldref_info是把在程式碼中引用的field(可能是本類的,也可能是外部類的)抽離成常量,與後面講的field_info不要混淆
4.9 CONSTANT_Methodref_info
CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}
複製程式碼

tag=10,表示一個引用method資訊,包括靜態method和例項method。

class_index是常量池中一個CONSTANT_Class_info型別常量(這裡只能是類)索引,表示method所屬類。name_and_type_index是常量池中一個CONSTANT_NameAndType_info型別常量索引,表示method的名稱和引數,返回值資訊。

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

tag=11,表示一個介面method資訊。

class_index是常量池中一個CONSTANT_Class_info型別常量(這裡只能是介面)索引,表示method所屬介面。name_and_type_index同CONSTANT_Methodref_info。

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

tag=12,儲存field或method的名稱,型別等資訊,可以看出它又是兩個引用。name_index指向一個CONSTANT_Utf8_info,表示欄位或方法的非全限定名稱。descriptor_index也指向一個CONSTANT_Utf8_info,表示該欄位/方法的描述資訊。

Descriptor

descriptor用一個字串CONSTANT_Utf8_info儲存。

  • 欄位描述符(FieldType),FieldType可以是基本型別:B(byte) C(char) D(double) F(float) I(int) J(long) S(short) Z(boolean),物件型別:L+全限定類名,陣列型別:[+元素型別

    int a; // I
    Integer b; //Ljava/lang/Integer
    double[] c; //[D
    double[][] d; //[[D
    Object[] e; //[Ljava/lang/Object
    Object[][][] f; //[[[Ljava/lang/Object
    複製程式碼
  • 方法描述符(MethodDescriptor),MethodDescriptor格式為(引數型別)返回型別

    /**
     * 描述符:(IDLjava/lang/Thread;)Ljava/lang/Object;
     */
    Object m(int i,double d,Thread t) {...}
    複製程式碼
4.12 CONSTANT_MethodHandle_info
CONSTANT_MethodHandle_info {
    u1 tag;
    u1 reference_kind;
    u2 reference_index;
}
複製程式碼

tag=15,方法控制程式碼,比如獲取一個類靜態欄位,例項欄位,呼叫一個方法,構造器等都會轉化成一個控制程式碼引用。

  • reference_kind

    Kind Description Interpretation
    1 REF_getField getfield C.f:T
    2 REF_getStatic getstatic C.f:T
    3 REF_putField putfield C.f:T
    4 REF_putStatic putstatic C.f:T
    5 REF_invokeVirtual invokevirtual C.m:(A*)T
    6 REF_invokeStatic invokestatic C.m:(A*)T
    7 REF_invokeSpecial invokespecial C.m:(A*)T
    8 REF_newInvokeSpecial new C; dup; invokespecial C.<init>:(A*)V
    9 REF_invokeInterface invokeinterface C.m:(A*)T

    f: field,m: method,:例項構造器

  • reference_index

    • 對於Kind=1,2,3,4,reference_index引用一個CONSTANT_Fieldref_info
    • 對於Kind=5,6,7,8,reference_index引用一個CONSTANT_Methodref_info
    • 對於Kind=9,reference_index引用一個CONSTANT_InterfaceMethodref_info
4.13 CONSTANT_MethodType_info
CONSTANT_MethodType_info {
    u1 tag;
    u2 descriptor_index;
}

複製程式碼

tag=16,描述一個方法型別。descriptor_index引用一個CONSTANT_Utf8_info,表示方法的描述符

4.14 CONSTANT_InvokeDynamic_info
CONSTANT_InvokeDynamic_info {
    u1 tag;
    u2 bootstrap_method_attr_index;
    u2 name_and_type_index;
}

複製程式碼

tag=18,invokedynamic動態呼叫指令引用資訊。

  • bootstrap_method_attr_index,BootstrapMethods屬性中bootstrap_methods[]陣列的索引,每個引導方法引用了CONSTANT_MethodHandle_info
  • name_and_type_index,引用一個CONSTANT_NameAndType_info常量
5. access flags

access flags表示類,介面,欄位,方法的訪問控制和修飾資訊。

Access Flag(u2) Value 作用物件
ACC_PUBLIC 0x0001 class,inner,field,method
ACC_PRIVATE 0x0002 inner,method
ACC_PROTECTED 0x0004 inner,method
ACC_STATIC 0x0008 inner,method
ACC_FINAL 0x0010 class,method
ACC_SUPER 0x0020 class
ACC_SYNCHRONIZED 0x0020 method
ACC_VOLATILE 0x0040 field
ACC_BRIDGE 0x0040 method
ACC_TRANSIENT 0x0080 field
ACC_VARARGS 0x0080 method
ACC_NATIVE 0x0100 method
ACC_INTERFACE 0x0200 class,inner
ACC_ABSTRACT 0x0400 class,method
ACC_STRICT 0x0800 method
ACC_SYNTHETIC 0x1000 class,method
ACC_ANNOTATION 0x2000 class,inner
ACC_ENUM 0x4000 class,field

其中大部分都能見名知意,補充以下幾點:

  • ACC_SUPER:用於invokespecial指令而需要特殊處理的父類方法
  • ACC_BRIDGE:橋方法標誌,有該標誌的方法上同時有ACC_SYNTHETIC標誌
  • ACC_STRICT:strictfp,strict float point,方法使用 FP-strict 浮點格式
  • ACC_SYNTHETIC:標誌是由編譯器生成的,原始碼中並沒有
6. this class

當前類或介面,指向一個CONSTANT_Class_info常量,可以從中解析當前類的全限定名稱。包名層次用/分割,而不是.,如java/lang/Object

7. super class

當前類的直接父類索引,指向一個CONSTANT_Class_info常量,當沒有直接父類時super_class=0

8. interfaces

首先用u2表明當前類或介面的直接父介面數量n。緊接著n個u2組成的陣列即是這些父介面在常量池的索引,型別是CONSTANT_Class_info,按宣告順序從左至右。

9. fields
field_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

複製程式碼

field_info儲存當前類的fields資訊。很簡單,其中大部分前面都講過了,關於attributes放在下文第11節專門講解。需要注意的是fields只包含當前類的欄位,如A的內部類B的欄位c,則是在類A$B中

10. methods
method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

複製程式碼

儲存當前類的方法資訊,同field_info

11. attributes

屬性表:屬性存在與ClassFile,field_info,method_info中,此外Code屬性中又包含巢狀屬性資訊,屬性用來描述指令碼,異常,註解,泛型等資訊,JLS8預定義了23種屬性,每種屬性結構不同(變長),但可以抽象成下面通用結構。

attribute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];
}

複製程式碼

attribute_name_index:是該屬性名稱在常量池中的索引,通過該名稱才可以判定當前屬性屬於具體哪一種,如“Code”表示當前是一個Code_attribute

attribute_length:表示接下來多少位元組是該屬性的內容資訊,java允許自定義新的屬性,如果jvm不認識,則按通用結構直接讀取attribute_length個位元組。

23種屬性按作用可以分為3組:

  • 被jvm翻譯使用:ConstantValue,Code,StackMapTable,Exceptions,BootstrapMethods
  • 被java類庫解析使用:InnerClasses,EnclosingMethod,Synthetic,Signature,RuntimeVisibleAnnotations/RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations/RuntimeInvisibleParameterAnnotations,RuntimeVisibleTypeAnnotations/RuntimeInvisibleTypeAnnotations,AnnotationDefault,MethodParameters
  • 既不要求jvm解析,也不要求java類庫解析,用於除錯工具等場景:SourceFile,SourceDebugExtension,LineNumberTable,LocalVariableTable,LocalVariableTypeTable,Deprecated

注:後面我會介紹如何解析class,所以本文只對每個屬性的結構和作用做一個簡單介紹

11.1 ConstantValue
ConstantValue_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 constantvalue_index;
}

複製程式碼

存在於field_info,代表一個常量值,如private final int x = 5中的5。attribute_name_index引用的值是“ConstantValue”,attribute_length固定為2,接下來兩個位元組的constantvalue_index是該常量值在常量池中的索引,是CONSTANT_Long,CONSTANT_Float,CONSTANT_Double,CONSTANT_Integer,CONSTANT_String的一種。

11.2 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];
}

複製程式碼

描述方法體編譯後的位元組碼指令。前面講過描述方法的method_info結構,而方法的方法體資訊就存在它的屬性表中code屬性內。如果是抽象方法,那就沒有這個屬性。

前面在講屬性通用結構attribute_info的時候已經講過attribute_name_index,attribute_length,它是每個屬性都有的,下文就不在說明瞭,只對其他部分介紹。

  • max_stack , 運算元棧的最大深度,用來分配棧的大小

  • max_locals, 方法棧幀中區域性變量表最大容量,儲存區域性變數,方法引數,異常引數等。以slot為單位,32bit以內的變數用分配1個slot,大於32bit,如long、double分配2個slot,注意物件存的是引用。另外指出一點,對於例項方法,預設會傳入this物件指標,所以這時的max_locals最小為1。

  • code[code_length],儲存位元組碼指令列表,每條位元組碼指令是一個byte,這樣8bit最多可以表示256條不同指令,需要指出的是這個位元組流陣列存的不全是指令,有的指令還有對應的運算元,跳過相應n個位元組的運算元再往後才是下一條指令,詳細內容我會在另外的文章中演示。

  • exception_table[exception_table_length],方法異常表,注意不是方法宣告丟擲的異常,而是顯示try-catch的異常,每個catch的異常時exception_table的一項。

    • catch_type,捕獲的異常型別,指向一個CONSTANT_Class_info常量
    • start_pc,位元組碼指令相對方法開始的偏移量,相當於code[code_length]中的索引
    • end_pc, 位元組碼指令相對方法開始的偏移量,相當於code[code_length]中的索引
    • handler_pc,位元組碼指令相對方法開始的偏移量,相當於code[code_length]中的索引

    這幾項表示的意思是:如果在[start_pc,end_pc)區間發生了catch_type型別或其子類的異常(catch_type=0表示捕獲任意異常),則跳轉至handler_pc處的指令繼續執行。

    補充三點:

    1)關於finaly塊中的指令採用的方式是在每個程式碼分支中冗餘一份。

    2)關於未顯示捕獲的異常則通過athrow指令繼續丟擲

    3)雖然指令長度code_length是u4,但start_pc,end_pc,handler_pc都只有2個位元組的無符號數u2,最大表示範圍只有65535,因此方法最多隻能有65535條指令(每條指令都不帶運算元的情況下)

  • attributes[attributes_count],巢狀屬性列表

11.3 StackMapTable
StackMapTable_attribute {
    u2              attribute_name_index;
    u4              attribute_length;
    u2              number_of_entries;
    stack_map_frame entries[number_of_entries];
}

複製程式碼

上面講到Code_attribute中也可以包含屬性表,StackMapTable就位於Code屬性的屬性表中,它是為了在jvm位元組碼驗證階段做型別推導驗證而新增的

11.4 Exceptions
Exceptions_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 number_of_exceptions;
    u2 exception_index_table[number_of_exceptions];
}

複製程式碼

表示通過throws宣告的可能丟擲的異常,結構很簡單exception_index_table每一項u2指向一個CONSTANT_Class_info常量

11.5 BootstrapMethods
BootstrapMethods_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 num_bootstrap_methods;
    {   u2 bootstrap_method_ref;
        u2 num_bootstrap_arguments;
        u2 bootstrap_arguments[num_bootstrap_arguments];
    } bootstrap_methods[num_bootstrap_methods];
}

複製程式碼

位於ClassFile中,儲存 invokedynamic 指令引用的引導方法

  • bootstrap_method_ref,引用一個一個 CONSTANT_MethodHandle_info 常量,此時該MethodHandle的reference_kind 必定為REF_invokeStatic或REF_newInvokeSpecial
  • num_bootstrap_arguments,引導方法引數列表,陣列中每一項是一個 CONSTANT_String_info,CONSTANT_Class_info,CONSTANT_Integer_info,CONSTANT_Long_info,CONSTANT_Float_info,CONSTANT_Double_info,CONSTANT_MethodHandle_info,or CONSTANT_MethodType_info引用
11.6 InnerClasses
InnerClasses_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 number_of_classes;
    {   u2 inner_class_info_index;
        u2 outer_class_info_index;
        u2 inner_name_index;
        u2 inner_class_access_flags;
    } classes[number_of_classes];
}
複製程式碼

記錄內部類資訊,classes就是當前類的內部類列表,其中inner_class_info_index,outer_class_info_index指向CONSTANT_Class型常量,分別代表內部類和外部類資訊引用,inner_name_index是內部類名稱的引用(CONSTANT_Utf8_info),等於0則代表是匿名內部類,inner_class_access_flags是內部類訪問標誌,同access_flags

11.7 EnclosingMethod
EnclosingMethod_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 class_index;
    u2 method_index;
}
複製程式碼

位於ClassFile結構中,儲存區域性類或匿名類資訊。

  • class_index,對直接包含它的類的引用,引用一個CONSTANT_Class_info常量,代表包含當前類宣告的最內層類
  • method_index,引用一個CONSTANT_NameAndType_info常量,表示直接包含該區域性類、匿名類的方法名稱和型別
11.8 Synthetic
Synthetic_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
}

複製程式碼

標記是否類、方法、欄位為編譯器生成,與ACC_SYNTHETIC同義,attribute_length=0,存在該屬性則表示true。

11.9 Signature
Signature_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 signature_index;
}

複製程式碼

存在於類,方法,欄位的屬性表中,用於儲存類,方法,欄位的泛型資訊(型別變數Type Variables,引數化型別Parameterized Types)。

關於泛型可以參考這裡

  • signature_index,引用一個CONSTANT_Utf8_info常量,表示簽名
11.10 RuntimeVisibleAnnotations
RuntimeVisibleAnnotations_attribute {
    u2         attribute_name_index;
    u4         attribute_length;
    u2         num_annotations;
    annotation annotations[num_annotations];
}

複製程式碼

存在於類,方法,欄位,儲存執行時可見的(RetentionPolicy.RUNTIME)註解資訊,可以被反射API獲取到,關於註解可以參考這裡

annotation結構儲存了註解名稱,元素值對的資訊,具體可以參考官方檔案,或者我後面class解析的文章

11.11 RuntimeInvisibleAnnotations
RuntimeInvisibleAnnotations_attribute {
    u2         attribute_name_index;
    u4         attribute_length;
    u2         num_annotations;
    annotation annotations[num_annotations];
}

複製程式碼

與RuntimeVisibleAnnotations結構相同,但不可見,即不能被反射API獲取到,目前jvm忽略此屬性

11.12 RuntimeVisibleParameterAnnotations
RuntimeVisibleParameterAnnotations_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 num_parameters;
    {   u2         num_annotations;
        annotation annotations[num_annotations];
    } parameter_annotations[num_parameters];
}

複製程式碼

存在於method_info的屬性表中,儲存執行時可見的方法引數註解資訊,與RuntimeVisibleAnnotations對比發現,RuntimeVisibleParameterAnnotations儲存的是方法的引數列表上每個引數的註解(相當與一組RuntimeVisibleParameterAnnotations),順序與方法描述符中引數順序一致

11.13 RuntimeInvisibleParameterAnnotations
RuntimeInvisibleParameterAnnotations_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 num_parameters;
    {   u2         num_annotations;
        annotation annotations[num_annotations];
    } parameter_annotations[num_parameters];
}
複製程式碼

不想再囉嗦了

11.14 RuntimeVisibleTypeAnnotations
RuntimeVisibleTypeAnnotations_attribute {
    u2              attribute_name_index;
    u4              attribute_length;
    u2              num_annotations;
    type_annotation annotations[num_annotations];
}
複製程式碼

存在於class_file,method_info,field_info,code的屬性表中,java8新增。JLS8新增兩種ElementType(ElementType.TYPE_PARAMETER,ElementType.TYPE_USE),相應用來描述的註解屬性也做了相應的改的,就有了該屬性,type_annotation儲存著註解資訊及其作用物件。

11.15 RuntimeInvisibleTypeAnnotations
RuntimeInvisibleTypeAnnotations_attribute {
    u2              attribute_name_index;
    u4              attribute_length;
    u2              num_annotations;
    type_annotation annotations[num_annotations];
}
複製程式碼

略。。。

11.16 AnnotationDefault
AnnotationDefault_attribute {
    u2            attribute_name_index;
    u4            attribute_length;
    element_value default_value;
}

複製程式碼

存在於method_info屬性表 ,記錄註解元素的預設值

11.17 MethodParameters
MethodParameters_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 parameters_count;
    {   u2 name_index;
        u2 access_flags;
    } parameters[parameters_count];
}

複製程式碼

存在於method_info屬性表 ,記錄方法引數資訊,name_index形參名稱,access_flags有ACC_FINAL,ACC_SYNTHETIC,ACC_MANDATED

11.18 SourceFile
SourceFile_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 sourcefile_index;
}

複製程式碼

class_file屬性表中,記錄生成該的檔名,異常堆疊可能顯示此資訊,一般與類名相同,但內部類不是。這是一個可選屬性,意味著不強制編譯器生成此資訊。

11.19 SourceDebugExtension
SourceDebugExtension_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 debug_extension[attribute_length];
}
複製程式碼

存在於class結構中,可選,儲存非java語言的擴充套件除錯資訊。debug_extension 陣列是指向CONSTAN_Utf8_info的索引

11.20 LineNumberTable
LineNumberTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 line_number_table_length;
    {   u2 start_pc;
        u2 line_number;	
    } line_number_table[line_number_table_length];
}
複製程式碼

code的屬性表中,儲存原始碼行號與位元組碼偏移量(方法第幾條指令)之間對映關係,start_pc位元組碼偏移量,line_number原始碼行號,可選。

問題:在錯誤堆疊中如何打印出出錯的原始碼行號的?如何支援在原始碼上斷點除錯?

11.21 LocalVariableTable
LocalVariableTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_table_length;
    {   u2 start_pc;
        u2 length;
        u2 name_index;
        u2 descriptor_index;
        u2 index;
    } local_variable_table[local_variable_table_length];
}
複製程式碼

code的屬性表中,儲存棧幀中區域性變量表的變數與原始碼中定義的變數的對映,可以在解析code屬性時關聯到區域性變量表變數在原始碼中的變數名等,可選。

11.22 LocalVariableTypeTable
LocalVariableTypeTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_type_table_length;
    {   u2 start_pc;
        u2 length;
        u2 name_index;
        u2 signature_index;
        u2 index;
    } local_variable_type_table[local_variable_type_table_length];
}
複製程式碼

code的屬性表中,與LocalVariableTable相似,signature_index也引用一個CONSTANT_Utf8_info 常量,對應含有泛型的變數會同時儲存到LocalVariableTable和LocalVariableTypeTable中個一份

11.23 Deprecated
Deprecated_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
}
複製程式碼

類、方法、欄位過期標記,沒有額外資訊,attribute_length=0,如果出現該屬性則說明加了@deprecated註解

完!如果覺得寫的還可以,給個贊鼓勵一下吧!


下期預告:動手編寫一個解析class(位元組碼)檔案的程式

關注微訊號,更多精彩等著你