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
,orCONSTANT_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(位元組碼)檔案的程式
關注微訊號,更多精彩等著你