上一章中介绍了字节码文件的基本结构,这一章我们介绍字节码文件中的属性,属性的通用格式如下:
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
-
attribute_name_index
: 属性名对应的常量池中CONSTANT_Utf8_info
类型常量的有效索引,值就是属性名,通过属性名区分不同属性。 -
attribute_length
: 属性数据的长度,不包括attribute_name_index
和attribute_length
这六个字节长度。 -
info[attribute_length]
: 属性数据的字节数组,每个属性数据大小不一样。
在 JVM8
中一共有 23
属性,分别是:
属性名 | 所在位置 | 描述 |
---|---|---|
SourceFile | ClassFile | 记录源码文件名 |
InnerClasses | ClassFile | 内部类的列表 |
EnclosingMethod | ClassFile | 当且仅当类为局部类或者匿名类时,才有这个属性,标记这个类所在的外部方法 |
SourceDebugExtension | ClassFile | 用于储存额外的调式信息;比如JSP 文件调试时,无法通过java 堆栈来定位JSP 文件行号。 |
BootstrapMethods | ClassFile | 引导方法列表,invokedynamic 指令就是和对应引导方法绑定 |
ConstantValue | field_info | 通过 final 关键字修饰的字段对应的常量值 |
Code | method_info | 方法对应字节码指令 |
Exceptions | method_info | 方法抛出的异常列表 |
RuntimeVisibleParameterAnnotations | method_info | 可见的方法参数注解 |
RuntimeInvisibleParameterAnnotations | method_info | 不可见的方法参数注解 |
AnnotationDefault | method_info | 记录注解类元素默认值 |
MethodParameters | method_info | 将方法参数参数名编译到字节码文件中(编译时加上 -parameters 参数) |
Synthetic | ClassFile, field_info, method_info | 标志字段,方法和类由编译器自动生成 |
Deprecated | ClassFile, field_info, method_info | 声明字段,方法和类将弃用 |
Signature | ClassFile, field_info, method_info | 记录字段,方法和类的泛型信息 |
RuntimeVisibleAnnotations | ClassFile, field_info, method_info | 可见的字段,方法和类注解 |
RuntimeInvisibleAnnotations | ClassFile, field_info, method_info | 不可见的字段,方法和类注解 |
LineNumberTable | Code |
java 源码中方法中字节指令和行号对应关系 |
LocalVariableTable | Code | 方法中局部变量描述 |
LocalVariableTypeTable | Code | 方法中泛型局部变量描述 |
StackMapTable | Code | 记录数据供类型检查验证器检查,来验证方法局部变量表和操作数栈所需类型是否匹配 |
RuntimeVisibleTypeAnnotations | ClassFile, field_info, method_info, Code | 可见的类型注解,主要用于实现 JSR 308
|
RuntimeInvisibleTypeAnnotations | ClassFile, field_info, method_info, Code | 不可见的类型注解,主要用于实现 JSR 308
|
这23
个属性分为三个部分:
- 对
java
虚拟机正确解读字节码文件起关键作用的5
个属性:ConstantValue
Code
StackMapTable
Exceptions
BootstrapMethods
- 对
java SE
能够正确解读字节码文件起关键作用的12
个属性:InnerClasses
EnclosingMethod
Synthetic
Signature
RuntimeVisibleAnnotations
RuntimeInvisibleAnnotations
RuntimeVisibleParameterAnnotations
RuntimeInvisibleParameterAnnotations
RuntimeVisibleTypeAnnotations
RuntimeInvisibleTypeAnnotations
AnnotationDefault
MethodParameters
- 对
java SE
解读字节码文件起工具性辅助作用的6
个属性:SourceFile
SourceDebugExtension
LineNumberTable
LocalVariableTable
LocalVariableTypeTable
Deprecated
一. 简单属性
1.1 ConstantValue
ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index;
}
这是一个定长属性,constantvalue_index
是常量池的一个有效索引,类型是基础类型和 CONSTANT_String_info
类型,通过final
关键字修饰的字段就会有这个值。
1.2 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];
}
这是一个变长属性,因为一个类可能有多个内部类:
-
number_of_classes
和classes[number_of_classes]
: 记录内部类的信息。-
inner_class_info_index
:CONSTANT_Class_info
类型常量的有效索引,表示内部类的符号引用。 -
outer_class_info_index
:CONSTANT_Class_info
类型常量的有效索引,表示外部类的符号引用。 -
inner_name_index
:CONSTANT_Utf8_info
类型常量的有效索引,表示内部类的简单名字。 -
inner_class_access_flags
: 表示内部类的访问标志。
-
1.3 EnclosingMethod
EnclosingMethod_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 class_index;
u2 method_index;
}
这个是定长属性,当且仅当类为局部类或者匿名类时,才有这个属性:
-
class_index
:CONSTANT_Class_info
类型常量的有效索引,是包含这个局部类或者匿名类的外部类的符号引用。 -
method_index
:CONSTANT_NameAndType_info
类型常量的有效索引,表示包含这个局部类或者匿名类方法的名称和描述符。但是如果这个方法是类初始化方法或者实例初始化方法中,那么这个值必须是0
。
1.4 Synthetic
Synthetic_attribute {
u2 attribute_name_index;
u4 attribute_length;
}
这个是定长属性,attribute_length
的值必须是 0
,表示字段,方法和类由编译器自动生成。
1.5 MethodParameters
MethodParameters_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 parameters_count;
{ u2 name_index;
u2 access_flags;
} parameters[parameters_count];
}
这是一个变长属性,记录方法中参数名称:
-
parameters_count
和parameters[parameters_count]
: 记录方法参数名列表:-
name_index
:CONSTANT_Utf8_info
类型常量的有效索引,表示方法参数名称。 -
access_flags
: 方法参数的访问标志。
-
1.6 SourceFile
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
这是一个定长属性,attribute_length
的值就是2
,sourcefile_index
是常量池中 CONSTANT_Utf8_info
类型常量的有效索引,值就是源文件的名字。
1.7 SourceDebugExtension
SourceDebugExtension_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 debug_extension[attribute_length];
}
这是一个变长属性,debug_extension[attribute_length]
字节数组记录额外的调式信息。
1.8 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];
}
这是一个变长属性,记录方法的字节指令地址对应源码行号关系:
-
line_number_table_length
和line_number_table[line_number_table_length]
: 字节指令地址和行号对应关系。-
start_pc
: 字节指令地址。 -
line_number
: 对应的源码行号。
-
1.9 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];
}
这是一个变长属性,记录方法中每个局部变量的描述符和作用范围:
-
local_variable_table_length
和local_variable_table[local_variable_table_length]
: 记录局部变量的描述符和作用范围。-
start_pc
: 开始的指令地址。 -
length
: 作用范围长度。 -
name_index
: 局部变量的名字。 -
descriptor_index
: 局部变量类型描述符。 -
index
: 局部变量在局部变量表的索引。
-
1.10 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];
}
这是一个变长属性,记录方法中具有泛型的局部变量的泛型签名和作用范围:
-
local_variable_type_table_length
和local_variable_type_table[local_variable_type_table_length]
: 记录局部变量的泛型签名和作用范围。-
start_pc
: 开始的指令地址。 -
length
: 作用范围长度。 -
name_index
: 局部变量的名字。 -
signature_index
: 局部变量泛型签名。 -
index
: 局部变量在局部变量表的索引。
-
LocalVariableTypeTable
是LocalVariableTable
的补充,主要就是为了记录方法中具有泛型的局部变量的泛型签名信息。
1.11 Deprecated
Deprecated_attribute {
u2 attribute_name_index;
u4 attribute_length;
}
这个是定长属性,attribute_length
的值必须是 0
,声明字段,方法和类将弃用。
二. 方法相关
2.1 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];
}
记录方法相关数据:
-
max_stack
: 当前方法执行时最大操作数栈深度,和max_locals
一起用来计算方法对应虚拟机栈的栈帧大小。 -
max_locals
: 当前方法局部变量表的大小,和max_locals
一起用来计算方法对应虚拟机栈的栈帧大小。 -
code_length
和code[code_length]
: 记录方法编译后的指令数据。 -
exception_table_length
和exception_table[exception_table_length]
: 方法异常处理器。这个指定是方法中
try {} catch(){}
语句,而不是方法声明中throw
的异常。
-start_pc
: 开始指令地址,包括它。
-end_pc
: 结束指令地址,不包括它。
-handler_pc
: 跳转执行异常的指令地址。
-catch_type
:CONSTANT_Class_info
类型常量池有效索引,表示这个异常的类型。 -
attributes_count
和attributes[attributes_count]
: 表示方法的属性表。
public class T{
public static void main() {
int i = 0;
try {
i = 1;
} catch (Exception e1) {
i = 2;
}
}
}
对应的字节码:
public static void main();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=0
0: iconst_0
1: istore_0
2: iconst_1
3: istore_0
4: goto 10
7: astore_1
8: iconst_2
9: istore_0
10: return
Exception table:
from to target type
2 4 7 Class java/lang/Exception
LineNumberTable:
line 5: 0
line 7: 2
line 10: 4
line 8: 7
line 9: 8
line 11: 10
LocalVariableTable:
Start Length Slot Name Signature
8 2 1 e1 Ljava/lang/Exception;
2 9 0 i I
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 7
locals = [ int ]
stack = [ class java/lang/Exception ]
frame_type = 2 /* same */
可以分析得出:
- 方法的
max_stack
是1
;max_locals
是2
,因为是实例方法,局部变量表有一个this
变量;然后就是指令集。exception_table
只有一个,start_pc
是2
,end_pc
是4
,handler_pc
是7
, 捕捉的异常是Exception
类型;也就是说如果指令[2,4)过程中发生Exception
类型异常,那么就跳转到指令7
的位置开始执行。- 下面就是
Code
属性拥有的其他属性。
2.2 Exceptions
Exceptions_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_exceptions;
u2 exception_index_table[number_of_exceptions];
}
是一个变长属性,表示方法抛出的异常,exception_index_table[number_of_exceptions]
就是抛出异常类型列表,就方法上直接 throws
的异常列表。
2.3 StackMapTable
这个属性极其复杂,称为栈映射帧,主要用来加快字节码中方法指令类型检查的,我们先来看看它的结构:
StackMapTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_entries;
stack_map_frame entries[number_of_entries];
}
也就是说一个方法里可能有多个栈映射帧,它的数据结构就是 stack_map_frame
。
2.3.1 stack_map_frame
union stack_map_frame {
same_frame;
same_locals_1_stack_item_frame;
same_locals_1_stack_item_frame_extended;
chop_frame;
same_frame_extended;
append_frame;
full_frame;
}
栈映射帧一个有7
类型,每个类型格式如下:
same_frame {
u1 frame_type = SAME;/* 0-63 */
}
same_locals_1_stack_item_frame {
u1 frame_type = SAME_LOCALS_1_STACK_ITEM;/* 64-127 */
verification_type_info stack[1];
}
same_locals_1_stack_item_frame_extended {
u1 frame_type = SAME_LOCALS_1_STACK_ITEM_EXTENDED;/* 247 */
u2 offset_delta;
verification_type_info stack[1];
}
chop_frame {
u1 frame_type=CHOP; /* 248-250 */
u2 offset_delta;
}
same_frame_extended {
u1 frame_type = SAME_FRAME_EXTENDED;/* 251*/
u2 offset_delta;
}
append_frame {
u1 frame_type = APPEND; /* 252-254 */
u2 offset_delta;
verification_type_info locals[frame_type -251];
}
full_frame {
u1 frame_type = FULL_FRAME; /* 255 */
u2 offset_delta;
u2 number_of_locals;
verification_type_info locals[number_of_locals];
u2 number_of_stack_items;
verification_type_info stack[number_of_stack_items];
}
union verification_type_info {
Top_variable_info;
Integer_variable_info;
Float_variable_info;
Long_variable_info;
Double_variable_info;
Null_variable_info;
UninitializedThis_variable_info;
Object_variable_info;
Uninitialized_variable_info;
}
// Top_variable_info类型说明这个局部变量拥有验证类型top(ᴛ)。
Top_variable_info {
u1 tag = ITEM_Top; /* 0 */
}
// Integer_variable_info类型说明这个局部变量包含验证类型int
Integer_variable_info {
u1 tag = ITEM_Integer; /* 1 */
}
//Float_variable_info类型说明局部变量包含验证类型float
Float_variable_info {
u1 tag = ITEM_Float; /* 2 */
}
// Long_variable_info结构在局部变量表或操作数栈中占用2个存储单元。
Long_variable_info {
u1 tag = ITEM_Long; /* 4 */
}
// Double_variable_info结构在局部变量表或操作数栈中占用2个存储单元。
Double_variable_info {
u1 tag = ITEM_Double; /* 3 */
}
// Null_variable_info类型说明存储单元包含验证类型null。
Null_variable_info {
u1 tag = ITEM_Null; /* 5 */
}
// UninitializedThis_variable_info类型说明存储单元包含验证类型uninitializedThis。
UninitializedThis_variable_info {
u1 tag = ITEM_UninitializedThis; /* 6 */
}
// Object_variable_info类型说明存储单元包含某个Class的实例。由常量池在cpool_index给出的索引处的CONSTANT_CLASS_Info(§4.4.1)结构表示。
Object_variable_info {
u1 tag = ITEM_Object; /* 7 */
u2 cpool_index;
}
// Uninitialized_variable_info说明存储单元包含验证类型uninitialized(offset)。offset项给出了一个偏移量,表示在包含此StackMapTable属性的Code属性中,new指令创建的对象所存储的位置。
Uninitialized_variable_info {
u1 tag = ITEM_Uninitialized /* 8 */
u2 offset;
}
2.3.2 stack_map_frame
那个格式作用
其实最主要的就是栈映射帧的三个属性:
-
offset_delta
: 表示当前这个栈映射帧的增量偏移量,用于计算每个帧在运行时的实际字节码偏移量。使用时帧的字节偏移量计算方法为: 前一帧的字节码偏移量(
Bytecode Offset
)加上offset_delta
的值再加1
,如果前一个帧是方法的初始帧(Initial Frame
),那这时候字节码偏移量就是offset_delta
。 -
locals[]
: 记录方法运行到这里,本地变量表中变量情况,类型是verification_type_info
。 -
stack[]
: 这个和方法当前操作数栈没有关系,主要是和异常有关,就是当前捕捉到的异常。
那为什么要分为 7
中类型,其实就是为了节省数据,虽然说 full_frame
可以表示任何情况下的栈映射帧,但是它浪费了太多数据,只有在其他类型不能满足的情况下,才会使用 full_frame
类型。
根据和上一个栈映射帧的 locals[]
和 stack[]
不同,分为四种情况:
- 有相同的
locals[]
,且stack[]
为空,那么就使用same_frame
和same_frame_extended
。
same_frame
只有一个字节的值,而且范围就是0-63
,超过63
就是另一个类型了,就用它表示当前栈映射帧的offset_delta
。
same_frame_extended
是same_frame
的扩展,因为same_frame
类型的offset_delta
最大只能是63
。
- 有相同的
locals[]
,且stack[]
不为空,长度为1
,那么就使用same_locals_1_stack_item_frame
和same_locals_1_stack_item_frame_extended
。
same_locals_1_stack_item_frame
的frame_type
值是64-127
,也就是说frame_type - 64
就是当前栈映射帧的offset_delta
。
same_locals_1_stack_item_frame_extended
是same_locals_1_stack_item_frame
的扩展。
-
stack[]
为空但是局部变量减少了,那么就使用chop_frame
。
frame_type
的范围是248-250
,而251 - frame_type
表示减少的局部变量值,也就是说最多只能是3
个,减少的局部变量超过3
个,那么就要使用full_frame
类型。
-
stack[]
为空但是有新的局部变量添加到locals[]
,那么就使用append_frame
。
frame_type
的范围是252-254
,而frame_type - 251
表示增加的局部变量值,也就是说最多只能是3
个,增加的局部变量超过3
个,那么就要使用full_frame
类型。
locals[frame_type -251]
表示增加的局部变量情况。
2.3.3 例子
public class T{
public void test() {
int i1 = 0;
if (i1 > 0) {}
i1 = 0;
if (i1 > 0) {}
}
}
对应的字节码:
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: ifle 6
6: iconst_0
7: istore_1
8: iload_1
9: ifle 12
12: return
LineNumberTable:
line 8: 0
line 9: 2
line 10: 6
line 11: 8
line 12: 12
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 this Lcom/zhang/jvm/reflect/_3/T;
2 11 1 i1 I
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 6
locals = [ int ]
frame_type = 5 /* same */
使用
if (i1 > 0) {}
条件判断进行程序跳转,就会产生两个栈映射帧,第一个肯定是append_frame
类型,因为定义了i1
变量;第二个就是same_frame
类型。
public class T{
public void test() {
int i1 = 0;
if (i1 > 0) {}
i1 = 0;
if (i1 > 0) {}
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
i1 = 0;
if (i1 > 0) {}
}
}
对应字节码就是
StackMapTable: number_of_entries = 3
frame_type = 252 /* append */
offset_delta = 6
locals = [ int ]
frame_type = 5 /* same */
frame_type = 251 /* same_frame_extended */
offset_delta = 65
我们使用多个
i1 = 0;
来增加指令的个数,这样我们就看到了same_frame_extended
类型。
public class T{
public void test() {
for (int i = 0; i < 2; i++) {
}
}
}
对应字节码:
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: iconst_2
4: if_icmpge 13
7: iinc 1, 1
10: goto 2
13: return
LineNumberTable:
line 7: 0
line 9: 13
LocalVariableTable:
Start Length Slot Name Signature
2 11 1 i I
0 14 0 this Lcom/zhang/jvm/reflect/_3/T;
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 2
locals = [ int ]
frame_type = 250 /* chop */
offset_delta = 10
因为循环完成后,
locals[]
中少了int
变量,那么就出现了chop_frame
类型。
public class T{
public void test() {
int i = 0;
try {
i = 1;
} catch (IndexOutOfBoundsException e) {
i = 2;
} catch (Exception e) {
i = 3;
}
}
}
对应字节码
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=3, args_size=1
0: iconst_0
1: istore_1
2: iconst_1
3: istore_1
4: goto 16
7: astore_2
8: iconst_2
9: istore_1
10: goto 16
13: astore_2
14: iconst_3
15: istore_1
16: return
Exception table:
from to target type
2 4 7 Class java/lang/IndexOutOfBoundsException
2 4 13 Class java/lang/Exception
LineNumberTable:
line 7: 0
line 9: 2
line 14: 4
line 10: 7
line 11: 8
line 14: 10
line 12: 13
line 13: 14
line 15: 16
LocalVariableTable:
Start Length Slot Name Signature
8 2 2 e Ljava/lang/IndexOutOfBoundsException;
14 2 2 e Ljava/lang/Exception;
0 17 0 this Lcom/zhang/jvm/reflect/_3/T;
2 15 1 i I
StackMapTable: number_of_entries = 3
frame_type = 255 /* full_frame */
offset_delta = 7
locals = [ class com/zhang/jvm/reflect/_3/T, int ]
stack = [ class java/lang/IndexOutOfBoundsException ]
frame_type = 69 /* same_locals_1_stack_item */
stack = [ class java/lang/Exception ]
frame_type = 2 /* same */
三. 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];
}
这是一个变长属性,记录引导方法列表:
-
num_bootstrap_methods
: 表示引导方法个数。 -
bootstrap_methods[num_bootstrap_methods]
: 引导方法信息。-
bootstrap_method_ref
:CONSTANT_MethodHandle_info
类型常量的有效索引。 -
bootstrap_arguments
: 引导方法的额外参数个数。 -
bootstrap_arguments[num_bootstrap_arguments]
: 额外参数数据,类型应该是CONSTANT_String_info
,CONSTANT_Class_info
,CONSTANT_Integer_info
,CONSTANT_Long_info
,CONSTANT_Float_info
,CONSTANT_Double_info
,CONSTANT_MethodHandle_info
和CONSTANT_MethodType_info
类型。
-
public class T{
public void test() {
Runnable run = () -> {};
run.run();
}
}
对应字节码:
Classfile /Users/zhangxinhao/work/java/test/example/jvm/build/classes/java/main/com/zhang/jvm/reflect/_3/T.class
Last modified 2021-12-12; size 984 bytes
MD5 checksum 9920a59a604240e9009aeaf197dcf5b6
Compiled from "T.java"
public class com.zhang.jvm.reflect._3.T
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#19 // java/lang/Object."<init>":()V
#2 = InvokeDynamic #0:#24 // #0:run:()Ljava/lang/Runnable;
#3 = InterfaceMethodref #25.#26 // java/lang/Runnable.run:()V
#4 = Class #27 // com/zhang/jvm/reflect/_3/T
#5 = Class #28 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 LocalVariableTable
#11 = Utf8 this
#12 = Utf8 Lcom/zhang/jvm/reflect/_3/T;
#13 = Utf8 test
#14 = Utf8 runnable
#15 = Utf8 Ljava/lang/Runnable;
#16 = Utf8 lambda$test$0
#17 = Utf8 SourceFile
#18 = Utf8 T.java
#19 = NameAndType #6:#7 // "<init>":()V
#20 = Utf8 BootstrapMethods
#21 = MethodHandle #6:#29 // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#22 = MethodType #7 // ()V
#23 = MethodHandle #6:#30 // invokestatic com/zhang/jvm/reflect/_3/T.lambda$test$0:()V
#24 = NameAndType #31:#32 // run:()Ljava/lang/Runnable;
#25 = Class #33 // java/lang/Runnable
#26 = NameAndType #31:#7 // run:()V
#27 = Utf8 com/zhang/jvm/reflect/_3/T
#28 = Utf8 java/lang/Object
#29 = Methodref #34.#35 // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#30 = Methodref #4.#36 // com/zhang/jvm/reflect/_3/T.lambda$test$0:()V
#31 = Utf8 run
#32 = Utf8 ()Ljava/lang/Runnable;
#33 = Utf8 java/lang/Runnable
#34 = Class #37 // java/lang/invoke/LambdaMetafactory
#35 = NameAndType #38:#42 // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#36 = NameAndType #16:#7 // lambda$test$0:()V
#37 = Utf8 java/lang/invoke/LambdaMetafactory
#38 = Utf8 metafactory
#39 = Class #44 // java/lang/invoke/MethodHandles$Lookup
#40 = Utf8 Lookup
#41 = Utf8 InnerClasses
#42 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#43 = Class #45 // java/lang/invoke/MethodHandles
#44 = Utf8 java/lang/invoke/MethodHandles$Lookup
#45 = Utf8 java/lang/invoke/MethodHandles
{
public com.zhang.jvm.reflect._3.T();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/zhang/jvm/reflect/_3/T;
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=1
0: invokedynamic #2, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
5: astore_1
6: aload_1
7: invokeinterface #3, 1 // InterfaceMethod java/lang/Runnable.run:()V
12: return
LineNumberTable:
line 7: 0
line 8: 6
line 9: 12
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 this Lcom/zhang/jvm/reflect/_3/T;
6 7 1 runnable Ljava/lang/Runnable;
private static void lambda$test$0();
descriptor: ()V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=0, locals=0, args_size=0
0: return
LineNumberTable:
line 7: 0
}
SourceFile: "T.java"
InnerClasses:
public static final #40= #39 of #43; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #21 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#22 ()V
#23 invokestatic com/zhang/jvm/reflect/_3/T.lambda$test$0:()V
#22 ()V
先看一下 test()
方法
0: invokedynamic #2, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
5: astore_1
6: aload_1
7: invokeinterface #3, 1 // InterfaceMethod java/lang/Runnable.run:()V
12: return
- 使用了
invokedynamic
指令,#2
是常量池索引为2
的值,是一个CONSTANT_InvokeDynamic_info
类型, 指向第一个引导方法,而且方法名是run
,方法描述符是()Ljava/lang/Runnable
,也就是说这个引导方法会返回一个Runnable
实例。astore_1
将引导方法返回的Runnable
实例存储到局部变量runnable
中。invokeinterface
直接调用这个Runnable
接口实例的runnable
的run
方法。
再看看引导方法
BootstrapMethods:
0: #21 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#22 ()V
#23 invokestatic com/zhang/jvm/reflect/_3/T.lambda$test$0:()V
#22 ()V
-
#21
就是bootstrap_method_ref
,是一个CONSTANT_MethodHandle_info
类型,就是LambdaMetafactory
的静态方法metafactory
,返回值是CallSite
类型。java
会通过字节码生成一个实现Runnable
接口的类,并从这个自生成类中查找run
方法的引用,由这个方法引用创建一个CallSite
类型实例,这样就可以通过方法引用直接调用这个自生成类的run
方法。 -
#22
,#23
和#22
都是这个引导方法的参数列表,类型分别是CONSTANT_MethodHandle_info
,CONSTANT_MethodType_info
和CONSTANT_MethodHandle_info
类型。#23
就是当前类 (T
) 的lambda$test$0
方法,这个是由编译器自动生成的方法。
生成类的代码:
import java.lang.invoke.LambdaForm.Hidden;
// $FF: synthetic class
final class T$$Lambda$1 implements Runnable {
private T$$Lambda$1() {
}
@Hidden
public void run() {
T.lambda$test$0();
}
}
直接调用当前类
T
由编译器自动生成的静态方法lambda$test$0
,而这个方法引用是通过引导方法参数传递进去的。
四. Signature
Signature_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 signature_index;
}
这是一个定长属性,大小就是8
个字节,signature_index
表示 CONSTANT_Utf8_info
类型常量的有效索引,就是这个签名信息字符串。
我们介绍字段和方法描述符的时候,提到过描述符只能体现四种类型:
-
BaseType
: 基础类型,分别是B C D F I J S Z L
。 -
ObjectType
: 引用类型,格式是LClassName;
,即以L
开头,以;
结尾,中间是类的全限定名。 -
ArrayType
: 数组类型,格式是[ComponentType
,即以[
开头,ComponentType
表示三个类型中任意类型。 -
V
: 就是void
,这个比较特殊,单指方法返回值为空。
但是我们知道从 java5.0
开始,java
语言开始支持泛型,那这个泛型信息记录在哪里呢?其实都记录在 Signature
属性中。
4.1 类型签名
1. JavaTypeSignature -> ReferenceTypeSignature | BaseType
2. BaseType -> B | C | D | F | I | J | S | Z
3. ReferenceTypeSignature -> ClassTypeSignature
| TypeVariableSignature
| ArrayTypeSignature
4. ClassTypeSignature -> L [PackageSpecifier]
SimpleClassTypeSignature {ClassTypeSignatureSuffix} ;
5. PackageSpecifier -> Identifier / {PackageSpecifier}
6. SimpleClassTypeSignature -> Identifier [TypeArguments]
7. TypeArguments -> < TypeArgument {TypeArgument} >
8. TypeArgument -> [WildcardIndicator] ReferenceTypeSignature | *
9. WildcardIndicator -> + | -
10. ClassTypeSignatureSuffix -> . SimpleClassTypeSignature
11. TypeVariableSignature -> T Identifier ;
12. ArrayTypeSignature -> [ JavaTypeSignature
类型签名的语法推导公式比较复杂,一共有12
个,它规定了 java
能够使用的泛型类型的形式,下面我们将一个一个分析。
4.1.1 JavaTypeSignature
JavaTypeSignature -> ReferenceTypeSignature | BaseType
表示 java
类型签名,分为引用类型签名ReferenceTypeSignature
和基础类型签名BaseType
。
- 可能大家有疑惑,签名信息怎么会有基础类型,在常规理解中,基础类型是不能用作泛型的啊;例如
List<int>
这个是不允许的。- 但是其实有两处,基础类型是可以放到泛型信息中的。
- 一个是基础类型数组,因为数组是一个引用类型,例如
List<int[]>
这个是允许的。- 一个是方法签名,泛型方法中是可以有基础类型参数的。
4.1.2 BaseType
BaseType -> B | C | D | F | I | J | S | Z
- 一共分为八种,分别是
B C D F I J S Z
。- 其中
Z
表示boolean
类型,因为B
已经表示byte
类型了;J
表示long
类型,因为L
这个字符被引用类型用了。
4.1.3 ReferenceTypeSignature
ReferenceTypeSignature -> ClassTypeSignature
| TypeVariableSignature
| ArrayTypeSignature
表示引用类型签名,分为三种:
-
ClassTypeSignature
: 类或者接口类型签名。 -
TypeVariableSignature
: 类型变量类型签名,主要定义泛型类和泛型方法时的类型变量。例如class User<T>
,这里面的T
就是类型变量。 -
ArrayTypeSignature
: 数组类型签名。
4.1.4 ClassTypeSignature
ClassTypeSignature -> L [PackageSpecifier]
SimpleClassTypeSignature {ClassTypeSignatureSuffix} ;
表示类或者接口类型签名,分为四个部分:
-
L
: 类或者接口类型签名必须是以L
开头。 -
[PackageSpecifier]
: 表示这个类或者接口的包名,因为包名有可能有,也有可能没有,所以使用[]
表示出现0
次或者1
次。 -
SimpleClassTypeSignature
: 类简单签名信息。 -
{ClassTypeSignatureSuffix}
: 这个是内部类的签名信息,因此内部类的内部类,因此这里使用{}
表示可以出现0
次或者多次。
例如:
public class T<U> {
T1<String>.T2<Number> t2;
class T1<U1> {
class T2<U2> {}
}
}
我们来看字段 t2
对应的类型签名 Lcom/zhang/jvm/reflect/_2/T<TU;>.T1<Ljava/lang/String;>.T2<Ljava/lang/Number;>;
可以分析得出:
com/zhang/jvm/reflect/_2/
就是PackageSpecifier
。T<TU;>
就是SimpleClassTypeSignature
,第一个T
表示这个类名,TU;
是一个TypeVariableSignature
签名。.T1<Ljava/lang/String;>
就是ClassTypeSignatureSuffix
。.T2<Ljava/lang/Number;>
就是ClassTypeSignatureSuffix
。
4.1.5 PackageSpecifier
PackageSpecifier -> Identifier / {PackageSpecifier}
就是包名信息,用/
作为标识符 Identifier
的分隔符。
4.1.6 SimpleClassTypeSignature
SimpleClassTypeSignature -> Identifier [TypeArguments]
类的简单签名,分为两部分:
-
Identifier
: 这个标识符就是类名,不包含包名的简单名字。 -
[TypeArguments]
: 表示参数化类型列表签名,因为可能有也可能没有,所以使用[]
表示出现0
次或者1
次。
4.1.7 TypeArguments
TypeArguments -> < TypeArgument {TypeArgument} >
表示参数化类型列表签名,分为三个部分:
-
< >
: 参数化类型列表签名必须以<
开头,以>
结尾。 -
TypeArgument
: 单个参数化类型签名。 -
{TypeArgument}
: 可能有多个参数化类型,所以使用{}
表示出现0
次或者多次。
例如 Map<String, Integer>
:
- 它的完整签名
Ljava/util/Map<Ljava/lang/String;Ljava/lang/Integer;>;
- 其中
<Ljava/lang/String;Ljava/lang/Integer;>
就是参数化类型列表签名。
4.1.8 TypeArgument
TypeArgument -> [WildcardIndicator] ReferenceTypeSignature | *
表示参数化类型签名,有两种类型:
-
[WildcardIndicator] ReferenceTypeSignature
: 上界通配符? extends
和下界通配符? super
,再加任意引用类型签名。 -
*
: 表示无界通配符;
例如:
-
List<?>
对应的签名信息就是Ljava/util/List<*>;
-
List<? extends Number>
对应的签名信息就是Ljava/util/List<+Ljava/lang/Number;>;
-
List<? super Number>
对应的签名信息就是Ljava/util/List<-Ljava/lang/Number;>;
4.1.9 WildcardIndicator
WildcardIndicator -> + | -
上界通配符 ? extends
对应 +
这个字符;下界通配符 ? super
对应-
这个字符。
4.1.10 ClassTypeSignatureSuffix
ClassTypeSignatureSuffix -> . SimpleClassTypeSignature
表示类型引用签名后缀,其实就是内部类的泛型签名信息,因为内部类肯定和外部类是同一个包的,所以这里需要 SimpleClassTypeSignature
,使用 .
开头作为 ClassTypeSignatureSuffix
开始。
4.1.11 TypeVariableSignature
TypeVariableSignature -> T Identifier ;
表示类型变量的签名,分为三个部分:
-
T
: 类型变量的签名开头字符就是T
。 -
Identifier
: 表示这个类型变量的名字。 -
;
: 类型变量结束符。
例如
public class T<U> {
U u;
}
这个字段 u
的签名信息就是类型变量类型签名 TU;
。
字段
u
的类型是Ljava/lang/Object;
,因为类签名T<U>
中这个U
就是Object
类型,这个我们下面在讲解类签名相关信息。
<M> M getM(){return null;}
这个泛型方法返回值 M
的类型变量类型签名就是 TM;
。
这个泛型方法完整签名是
<M:Ljava/lang/Object;>()TM;
4.1.12 ArrayTypeSignature
ArrayTypeSignature -> [ JavaTypeSignature
数组类型签名,以 [
开头,再加上任意java
类型签名 JavaTypeSignature
。
4.2 类签名
1. ClassSignature -> [TypeParameters] SuperclassSignature {SuperinterfaceSignature}
2. TypeParameters -> < TypeParameter {TypeParameter} >
3. TypeParameter -> Identifier ClassBound {InterfaceBound}
4. ClassBound -> : [ReferenceTypeSignature]
5. InterfaceBound -> : ReferenceTypeSignature
6. SuperclassSignature -> ClassTypeSignature
7. SuperinterfaceSignature -> ClassTypeSignature
4.2.1 ClassSignature
ClassSignature -> [TypeParameters] SuperclassSignature {SuperinterfaceSignature}
表示类签名,分为三部分:
-
[TypeParameters]
: 这个类类型变量相关参数信息,有了这个信息,我们使用类型变量类型签名TypeVariableSignature
时,才知道相关限制信息。 -
SuperclassSignature
: 父类签名信息,一定有这个信息。你可能疑惑,
Object
类就没有父类,那它的SuperclassSignature
不就没有么。但是Object
类不是泛型类。 -
{SuperinterfaceSignature}
: 表示实现接口的签名信息。
4.2.2 TypeParameters
TypeParameters -> < TypeParameter {TypeParameter} >
表示类签名中类型变量参数信息列表,因为泛型类可以有多个类型变量参数信息。
4.2.3 TypeParameter
TypeParameter -> Identifier ClassBound {InterfaceBound}
表示类签名中类型变量参数信息签名,分为三部分:
-
Identifier
: 表示类型变量名。 -
ClassBound
: 表示类型变量继承类的签名信息。 -
{InterfaceBound}
: 表示这个类型变量实现接口的签名信息,因为可以实现多个接口,所以这里使用{}
。
4.2.4 ClassBound
ClassBound -> : [ReferenceTypeSignature]
表示泛型类中类型变量的绑定类签名信息,它以 :
, 再加上任意一个java
引用类型签名 ReferenceTypeSignature
。
- 也就说不止是类或者接口类型签名
ClassTypeSignature
,还可以是类型变量类型签名TypeVariableSignature
和 数组类型签名ArrayTypeSignature
。- 为什么要使用
[]
, 当类型变量只实现接口的时候,那么绑定类签名信息就只有:
,而省略了它的父类Object
, 因为只有这样类型变量才会只体现绑定接口信息。
4.2.5 InterfaceBound
InterfaceBound -> : ReferenceTypeSignature
表示泛型类中类型变量的绑定接口签名信息,和ClassBound
基本一样。
例如
interface TI1 {}
interface TI2 {}
interface TI3 {}
interface TI4 {}
public class T<U1 extends Number & TI1 & TI2, U2 extends String & TI3 & TI4, U3 extends U2> {
U1 u1;
U2 u2;
U3 u3;
}
这个泛型类 T
的签名信息是 <U1:Ljava/lang/Number;:Lcom/zhang/jvm/reflect/_2/TI1;:Lcom/zhang/jvm/reflect/_2/TI2;U2:Ljava/lang/String;:Lcom/zhang/jvm/reflect/_2/TI3;:Lcom/zhang/jvm/reflect/_2/TI4;U3:TU2;>Ljava/lang/Object;
它分为以下几个部分:
<...>
里面就是TypeParameters
信息。U1: ...
是U1 extends Number & TI1 & TI2
对应的TypeParameter
信息。U2: ...
是U2 extends String & TI3 & TI4
对应的TypeParameter
信息。U3: ...
是U3 extends U2
对应的TypeParameter
信息。Ljava/lang/Object;
是SuperclassSignature
,即当前类父类类型签名。
对应的这三个字段:
-
u1
: 它的签名信息是TU1;
,而它的类型是Ljava/lang/Number;
。 -
u2
: 它的签名信息是TU2;
,而它的类型是Ljava/lang/String;
。 -
u3
: 它的签名信息是TU3;
,而它的类型是Ljava/lang/String;
。
4.2.6 SuperclassSignature
SuperclassSignature -> ClassTypeSignature
表示泛型类继承父类的类型签名。
注意它只能是类类型签名
ClassTypeSignature
,也就是说:
- 你不能继承一个类型变量,例如
class T<U> extends U
,这个是不允许的。- 你不能继承一个数组类型,例如
class T<U> extends Object[]
,这个也是不允许的。
要区分类签名 ClassSignature
和类类型签名ClassTypeSignature
的区别:
- 类签名
ClassSignature
: 记录定义泛型类时的相关信息,例如class T<U>
。 - 类类型签名
ClassTypeSignature
: 表示类或者接口类型签名,例如List<String>
。
4.2.7 SuperinterfaceSignature
SuperinterfaceSignature -> ClassTypeSignature
表示泛型类实现接口的类型签名,注意它只能是接口类型签名 ClassTypeSignature
。
4.3 方法签名
1. MethodSignature -> [TypeParameters] ( {JavaTypeSignature} ) Result {ThrowsSignature}
2. Result -> JavaTypeSignature | VoidDescriptor
3. ThrowsSignature -> ^ ClassTypeSignature | ^ TypeVariableSignature
4. VoidDescriptor -> V
4.3.1 MethodSignature
MethodSignature -> [TypeParameters] ( {JavaTypeSignature} ) Result {ThrowsSignature}
表示方法签名,分为四部分:
-
[TypeParameters]
: 表示方法签名中类型变量参数信息列表。 -
( {JavaTypeSignature} )
: 表示方法参数签名列表,方法参数签名可以是任意一个java
类型签名。 -
Result
: 表示方法返回值签名。 -
{ThrowsSignature}
: 表示方法抛出异常签名。
4.3.2 Result
Result -> JavaTypeSignature | VoidDescriptor
表示方法返回值签名,返回值可以是任意一个java
类型签名,和void
类型。
4.3.3 ThrowsSignature
ThrowsSignature -> ^ ClassTypeSignature | ^ TypeVariableSignature
表示方法抛出异常的签名,必须以 ^
符号开始,再加上异常类类型签名ClassTypeSignature
和 类型变量类型签名 TypeVariableSignature
(这个类型变量必须是 Throwable
子类)。
注意如果没有抛出类型变量类型异常的话,
ThrowsSignature
是没有值的。
4.3.4 VoidDescriptor
VoidDescriptor -> V
就是特指方法返回值 void
类型。
4.3.5 例子
public class T<U> {
<M> M getM(int age) throws Exception {
return null;
}
}
方法 getM
的签名就是 <M:Ljava/lang/Object;>(I)TM;
,分别是:
<M:Ljava/lang/Object;>
就是TypeParameters
,类型变量名字就是M
,父类类型就是Object
。
(I)
就是( {JavaTypeSignature} )
,方法参数签名列表。TM;
就是Result
,方法返回值签名,这里是类型变量类型签名。- 注意,这里虽然抛出了
Exception
异常,但是没有ThrowsSignature
签名信息。- 这个方法描述符是
(I)Ljava/lang/Object;
。
要想有 ThrowsSignature
签名,那必须:
public class T<U extends Throwable> {
<M> M getM(int age) throws Exception, U {
return null;
}
}
这个时候方法 getM
的签名就是 <M:Ljava/lang/Object;>(I)TM;^Ljava/lang/Exception;^TU4;
。
4.4 字段签名
1. FieldSignature -> ReferenceTypeSignature
字段签名就是任意一个引用类型签名ReferenceTypeSignature
,也就是说不包括基础类型BaseType
。
五. 注解
在字节码文件中,注解一共有7
类型,分别是
-
RuntimeVisibleAnnotations
和RuntimeInvisibleAnnotations
: 类,字段和方法的注解。-
Visible
表示运行期可见,即可以通过反射获取;Invisible
表示运行期不可见,即不能通过反射获取。 - 这里的类指的是
java
的四种类型,即class
,interface
,enum
和@interface
;都可以使用注解。
-
-
RuntimeVisibleParameterAnnotations
和RuntimeInvisibleParameterAnnotations
: 方法参数的注解。方法参数注解和普通注解的格式有点区别。
-
AnnotationDefault
: 注解默认值。它主要是记录注解方法的默认值。
-
RuntimeVisibleTypeAnnotations
和RuntimeInvisibleTypeAnnotations
: 类型注解,这个是1.8
提供的新功能。
5.1 RuntimeVisibleAnnotations
和 RuntimeInvisibleAnnotations
表示普通注解,可以修饰类,字段和方法;也就是说这两个属性可以出现在ClassFile
, field_info
和method_info
的属性表attributes
中。
5.1.1 RuntimeVisibleAnnotations_attribute
RuntimeVisibleAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
annotation annotations[num_annotations];
}
RuntimeInvisibleAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
annotation annotations[num_annotations];
}
这是一个变长属性,分别是:
-
attribute_name_index
: 属性名对应的常量池中CONSTANT_Utf8_info
类型常量的有效索引,值就是RuntimeVisibleAnnotations
或者RuntimeInvisibleAnnotations
。 -
attribute_length
: 表示当前这个属性去除attribute_name_index
和attribute_length
这6
个字节之后的剩余总字节数。 -
num_annotations
: 表示注解的个数,因为可以用多个注解修饰同一个类,字段或者方法。 -
annotations[num_annotations]
: 注解表,记录注解详细信息,类型是annotation
。
5.1.2 annotation
annotation
的格式如下:
annotation {
u2 type_index;
u2 num_element_value_pairs;
{ u2 element_name_index;
element_value value;
} element_value_pairs[num_element_value_pairs];
}
分为三部分:
-
type_index
: 对应的常量池中CONSTANT_Utf8_info
类型常量的有效索引,就是这个注解的全限定名称。 -
num_element_value_pairs
: 表示这个注解中键值对的个数。 -
element_value_pairs[num_element_value_pairs]
: 键值对表,记录注解键值对的详细信息,它包含两项内容:-
element_name_index
: 对应的常量池中CONSTANT_Utf8_info
类型常量的有效索引,就是键值对中键的名称。 -
value
: 表示键值对中值的数据,类型是element_value
。
-
5.1.3 element_value
element_value
的格式如下:
element_value {
u1 tag;
union {
u2 const_value_index;
{
u2 type_name_index;
u2 const_name_index;
} enum_const_value;
u2 class_info_index;
annotation annotation_value;
{
u2 num_values;
element_value values[num_values];
} array_value;
} value;
}
它就分为两个部分,tag
表示这个键值的类型;value
表示这个 键值的数据。
tag | Type | value | Constant Type |
---|---|---|---|
B | byte | const_value_index | CONSTANT_Integer |
C | char | const_value_index | CONSTANT_Integer |
D | double | const_value_index | CONSTANT_Double |
F | float | const_value_index | CONSTANT_Float |
I | int | const_value_index | CONSTANT_Integer |
J | long | const_value_index | CONSTANT_Long |
S | short | const_value_index | CONSTANT_Integer |
Z | boolean | const_value_index | CONSTANT_Integer |
s | String | const_value_index | CONSTANT_Utf8 |
e | Enum | enum_const_value | |
c | Class | class_info_index | |
@ | Annotation | annotation_value | |
[ | Array | array_value |
value
会根据tag
值来选择类型,一共有五种类型:
-
const_value_index
: 常量类型值。常量池中的一个消息消息索引,支持的类型有
byte
,char
,double
,float
,int
,long
,short
,boolean
和String
这九种常量。 -
enum_const_value
: 枚举类型值,包含两个数据:-
type_name_index
: 对应的常量池中CONSTANT_Utf8_info
类型常量的有效索引,就是这个枚举的全限定名称。 -
const_name_index
: 对应的常量池中CONSTANT_Utf8_info
类型常量的有效索引,这个枚举的值。
-
-
class_info_index
:Class
类型值。对应的常量池中
CONSTANT_Utf8_info
类型常量的有效索引,就是这个class
全限定名称。 -
annotation_value
: 注解类型值。它类型就是
annotation
,也就是说注解的键值可以是另一个注解。 -
array_value
: 数组类型值,包含两个数据:-
num_values
: 表示数组的长度。 -
values[num_values]
: 表叔数组中值的数据,它的类型是element_value
。
-
5.1.4 例子
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE, ElementType.PARAMETER})
@interface TA {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE, ElementType.PARAMETER})
@interface TA1 {
int age() default 10;
String name() default "zhang";
}
enum State {
START, END, STOP, RUNNING
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@interface TA2 {
int age();
String name();
State state() default State.START;
long[] ages();
Class<String> str();
TA ta();
TA1 ta1();
}
public class T {
@TA
private int field1;
@TA1(age = 100, name = "111")
private int field2;
@TA2(age = 1000, name = "222", state = State.END,
ages = 1234, str = String.class, ta = @TA, ta1 = @TA1)
private int field3;
}
对应的字节码:
Classfile /Users/zhangxinhao/work/java/test/example/jvm/build/classes/java/main/com/zhang/jvm/reflect/annotation/_1/T.class
Last modified 2021-12-7; size 753 bytes
MD5 checksum ada09cda423cba3f8c6efd806672822a
Compiled from "T.java"
public class com.zhang.jvm.reflect.annotation._1.T
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#37 // java/lang/Object."<init>":()V
#2 = Class #38 // com/zhang/jvm/reflect/annotation/_1/T
#3 = Class #39 // java/lang/Object
#4 = Utf8 field1
#5 = Utf8 I
#6 = Utf8 RuntimeVisibleAnnotations
#7 = Utf8 Lcom/zhang/jvm/reflect/annotation/_1/TA;
#8 = Utf8 field2
#9 = Utf8 Lcom/zhang/jvm/reflect/annotation/_1/TA1;
#10 = Utf8 age
#11 = Integer 100
#12 = Utf8 name
#13 = Utf8 111
#14 = Utf8 field3
#15 = Utf8 Lcom/zhang/jvm/reflect/annotation/_1/TA2;
#16 = Integer 1000
#17 = Utf8 222
#18 = Utf8 state
#19 = Utf8 Lcom/zhang/jvm/reflect/annotation/_1/State;
#20 = Utf8 END
#21 = Utf8 ages
#22 = Long 1234l
#24 = Utf8 str
#25 = Utf8 Ljava/lang/String;
#26 = Utf8 ta
#27 = Utf8 ta1
#28 = Utf8 <init>
#29 = Utf8 ()V
#30 = Utf8 Code
#31 = Utf8 LineNumberTable
#32 = Utf8 LocalVariableTable
#33 = Utf8 this
#34 = Utf8 Lcom/zhang/jvm/reflect/annotation/_1/T;
#35 = Utf8 SourceFile
#36 = Utf8 T.java
#37 = NameAndType #28:#29 // "<init>":()V
#38 = Utf8 com/zhang/jvm/reflect/annotation/_1/T
#39 = Utf8 java/lang/Object
{
private int field1;
descriptor: I
flags: ACC_PRIVATE
RuntimeVisibleAnnotations:
0: #7()
private int field2;
descriptor: I
flags: ACC_PRIVATE
RuntimeVisibleAnnotations:
0: #9(#10=I#11,#12=s#13)
private int field3;
descriptor: I
flags: ACC_PRIVATE
RuntimeVisibleAnnotations:
0: #15(#10=I#16,#12=s#17,#18=e#19.#20,#21=[J#22],#24=c#25,#26=@#7(),#27=@#9())
public com.zhang.jvm.reflect.annotation._1.T();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 40: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/zhang/jvm/reflect/annotation/_1/T;
}
SourceFile: "T.java"
从字节码中可以看出:
- 字段
field1
的注解信息0: #7()
里面一个注解信息,只有注解的全限定名称
Lcom/zhang/jvm/reflect/annotation/_1/TA;
, 没有注解的键值对。 - 字段
field2
的注解信息0: #9(#10=I#11,#12=s#13)
- 里面一个注解信息,包含注解的全限定名称
Lcom/zhang/jvm/reflect/annotation/_1/TA1;
- 还有两个键值对信息:
age=100
和name="111"
。
- 里面一个注解信息,包含注解的全限定名称
- 字段
field3
的注解信息0: #15(#10=I#16,#12=s#17,#18=e#19.#20,#21=[J#22],#24=c#25,#26=@#7(),#27=@#9())
里面一个注解信息,包含注解的全限定名称
Lcom/zhang/jvm/reflect/annotation/_1/TA2;
,还有七个键值对:-
#10=I#16
就是age=1000
。 -
#12=s#17
就是name ="222"
。 -
#18=e#19.#20
就是state=State.END
。 -
#21=[J#22]
就是ages=[1234]
。 -
#24=c#25
就是str=Ljava/lang/String;
。 -
#26=@#7()
就是ta=@TA
。 -
#27=@#9()
就是ta1=@TA1
。
-
5.2 RuntimeVisibleParameterAnnotations
和 RuntimeInvisibleParameterAnnotations
表示方法参数的注解,那么只能出现在method_info
的属性表attributes
中。
RuntimeVisibleParameterAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 num_parameters;
{ u2 num_annotations;
annotation annotations[num_annotations];
} parameter_annotations[num_parameters];
}
RuntimeInvisibleParameterAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 num_parameters;
{ u2 num_annotations;
annotation annotations[num_annotations];
} parameter_annotations[num_parameters];
}
这是一个变长属性,分别是:
-
attribute_name_index
: 属性名对应的常量池中CONSTANT_Utf8_info
类型常量的有效索引,值就是RuntimeVisibleParameterAnnotations
或者RuntimeInvisibleParameterAnnotations
。 -
attribute_length
: 表示当前这个属性去除attribute_name_index
和attribute_length
这 6 个字节之后的剩余总字节数。 -
num_parameters
: 表示方法参数使用注解的个数。 -
parameter_annotations[num_parameters]
: 方法参数注解的详细信息,它的值类型可以包含多个注解,因为一个方法参数上可以使用多个注解。-
num_annotations
: 表示一个方法参数上注解的个数。 -
annotations[num_annotations]
: 表示一个方法参数的注解表。
-
5.3 AnnotationDefault
表示注解方法的默认值信息,所以它也只能出现在method_info
的属性表attributes
中。
AnnotationDefault_attribute {
u2 attribute_name_index;
u4 attribute_length;
element_value default_value;
}
-
attribute_name_index
: 属性名对应的常量池中CONSTANT_Utf8_info
类型常量的有效索引,值就是AnnotationDefault
。 -
attribute_length
: 表示当前这个属性去除attribute_name_index
和attribute_length
这6
个字节之后的剩余总字节数。 -
default_value
: 注解方法返回的默认值,类型就是element_value
。
例子
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE, ElementType.PARAMETER})
public @interface TA {
int age();
String name();
int age1() default 10;
}
对应的字节码:
Classfile /Users/zhangxinhao/work/java/test/example/jvm/build/classes/java/main/com/zhang/jvm/reflect/annotation/_2/TA.class
Last modified 2021-12-7; size 542 bytes
MD5 checksum d06d22fdb1fc91c86e08a80d12a75fb6
Compiled from "TA.java"
public interface com.zhang.jvm.reflect.annotation._2.TA extends java.lang.annotation.Annotation
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
#1 = Class #24 // com/zhang/jvm/reflect/annotation/_2/TA
#2 = Class #25 // java/lang/Object
#3 = Class #26 // java/lang/annotation/Annotation
#4 = Utf8 age
#5 = Utf8 ()I
#6 = Utf8 name
#7 = Utf8 ()Ljava/lang/String;
#8 = Utf8 age1
#9 = Utf8 AnnotationDefault
#10 = Integer 10
#11 = Utf8 SourceFile
#12 = Utf8 TA.java
#13 = Utf8 RuntimeVisibleAnnotations
#14 = Utf8 Ljava/lang/annotation/Retention;
#15 = Utf8 value
#16 = Utf8 Ljava/lang/annotation/RetentionPolicy;
#17 = Utf8 RUNTIME
#18 = Utf8 Ljava/lang/annotation/Target;
#19 = Utf8 Ljava/lang/annotation/ElementType;
#20 = Utf8 METHOD
#21 = Utf8 FIELD
#22 = Utf8 TYPE
#23 = Utf8 PARAMETER
#24 = Utf8 com/zhang/jvm/reflect/annotation/_2/TA
#25 = Utf8 java/lang/Object
#26 = Utf8 java/lang/annotation/Annotation
{
public abstract int age();
descriptor: ()I
flags: ACC_PUBLIC, ACC_ABSTRACT
public abstract java.lang.String name();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_ABSTRACT
public abstract int age1();
descriptor: ()I
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: I#10}
SourceFile: "TA.java"
RuntimeVisibleAnnotations:
0: #14(#15=e#16.#17)
1: #18(#15=[e#19.#20,e#19.#21,e#19.#22,e#19.#23])
可以看出带默认返回值的方法 age1
就有 AnnotationDefault
属性,值就是 10
。
网友评论