JVM_字节码的属性

2021-12-12  本文已影响0人  wo883721

上一章中介绍了字节码文件的基本结构,这一章我们介绍字节码文件中的属性,属性的通用格式如下:

attribute_info {
   u2 attribute_name_index;
   u4 attribute_length;
   u1 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 个属性分为三个部分:

  1. java 虚拟机正确解读字节码文件起关键作用的5 个属性:
    • ConstantValue
    • Code
    • StackMapTable
    • Exceptions
    • BootstrapMethods
  2. java SE 能够正确解读字节码文件起关键作用的12 个属性:
    • InnerClasses
    • EnclosingMethod
    • Synthetic
    • Signature
    • RuntimeVisibleAnnotations
    • RuntimeInvisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • RuntimeInvisibleTypeAnnotations
    • AnnotationDefault
    • MethodParameters
  3. 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];
}

这是一个变长属性,因为一个类可能有多个内部类:

1.3 EnclosingMethod

EnclosingMethod_attribute {
   u2 attribute_name_index;
   u4 attribute_length;
   u2 class_index;
   u2 method_index;
}

这个是定长属性,当且仅当类为局部类或者匿名类时,才有这个属性:

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

这是一个变长属性,记录方法中参数名称:

1.6 SourceFile

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

这是一个定长属性,attribute_length 的值就是2sourcefile_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];
}

这是一个变长属性,记录方法的字节指令地址对应源码行号关系:

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

这是一个变长属性,记录方法中每个局部变量的描述符和作用范围:

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

这是一个变长属性,记录方法中具有泛型的局部变量的泛型签名和作用范围:

LocalVariableTypeTableLocalVariableTable 的补充,主要就是为了记录方法中具有泛型的局部变量的泛型签名信息。

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

记录方法相关数据:

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_stack1max_locals2,因为是实例方法,局部变量表有一个 this 变量;然后就是指令集。
  • exception_table 只有一个,start_pc2,end_pc4,handler_pc7, 捕捉的异常是 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 那个格式作用

其实最主要的就是栈映射帧的三个属性:

那为什么要分为 7 中类型,其实就是为了节省数据,虽然说 full_frame 可以表示任何情况下的栈映射帧,但是它浪费了太多数据,只有在其他类型不能满足的情况下,才会使用 full_frame 类型。

根据和上一个栈映射帧的 locals[]stack[] 不同,分为四种情况:

  1. 有相同的 locals[],且 stack[] 为空,那么就使用 same_framesame_frame_extended

same_frame 只有一个字节的值,而且范围就是 0-63,超过 63就是另一个类型了,就用它表示当前栈映射帧的offset_delta
same_frame_extendedsame_frame 的扩展,因为same_frame类型的offset_delta最大只能是 63

  1. 有相同的 locals[],且 stack[] 不为空,长度为1,那么就使用 same_locals_1_stack_item_framesame_locals_1_stack_item_frame_extended

same_locals_1_stack_item_frameframe_type 值是 64-127,也就是说 frame_type - 64 就是当前栈映射帧的offset_delta
same_locals_1_stack_item_frame_extendedsame_locals_1_stack_item_frame 的扩展。

  1. stack[] 为空但是局部变量减少了,那么就使用 chop_frame

frame_type 的范围是 248-250,而251 - frame_type 表示减少的局部变量值,也就是说最多只能是3 个,减少的局部变量超过3 个,那么就要使用 full_frame 类型。

  1. 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];
}

这是一个变长属性,记录引导方法列表:

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 接口实例的 runnablerun 方法。

再看看引导方法

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

生成类的代码:

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 类型常量的有效索引,就是这个签名信息字符串。

我们介绍字段和方法描述符的时候,提到过描述符只能体现四种类型:

但是我们知道从 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

表示引用类型签名,分为三种:

4.1.4 ClassTypeSignature

ClassTypeSignature -> L [PackageSpecifier]
                   SimpleClassTypeSignature {ClassTypeSignatureSuffix} ;

表示类或者接口类型签名,分为四个部分:

例如:

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]

类的简单签名,分为两部分:

4.1.7 TypeArguments

TypeArguments -> < TypeArgument {TypeArgument} >

表示参数化类型列表签名,分为三个部分:

例如 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 | *

表示参数化类型签名,有两种类型:

例如:

4.1.9 WildcardIndicator

WildcardIndicator -> + | -

上界通配符 ? extends 对应 + 这个字符;下界通配符 ? super 对应- 这个字符。

4.1.10 ClassTypeSignatureSuffix

ClassTypeSignatureSuffix -> . SimpleClassTypeSignature

表示类型引用签名后缀,其实就是内部类的泛型签名信息,因为内部类肯定和外部类是同一个包的,所以这里需要 SimpleClassTypeSignature ,使用 . 开头作为 ClassTypeSignatureSuffix开始。

4.1.11 TypeVariableSignature

TypeVariableSignature -> 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}

表示类签名,分为三部分:

4.2.2 TypeParameters

TypeParameters -> < TypeParameter {TypeParameter} >

表示类签名中类型变量参数信息列表,因为泛型类可以有多个类型变量参数信息。

4.2.3 TypeParameter

TypeParameter -> 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,即当前类父类类型签名。

对应的这三个字段:

4.2.6 SuperclassSignature

SuperclassSignature -> ClassTypeSignature

表示泛型类继承父类的类型签名。

注意它只能是类类型签名 ClassTypeSignature,也就是说:

  • 你不能继承一个类型变量,例如 class T<U> extends U,这个是不允许的。
  • 你不能继承一个数组类型,例如 class T<U> extends Object[],这个也是不允许的。

要区分类签名 ClassSignature 和类类型签名ClassTypeSignature 的区别:

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}

表示方法签名,分为四部分:

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 类型,分别是

5.1 RuntimeVisibleAnnotationsRuntimeInvisibleAnnotations

表示普通注解,可以修饰类,字段和方法;也就是说这两个属性可以出现在ClassFile, field_infomethod_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];
}

这是一个变长属性,分别是:

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

分为三部分:

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 值来选择类型,一共有五种类型:

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"

从字节码中可以看出:

5.2 RuntimeVisibleParameterAnnotationsRuntimeInvisibleParameterAnnotations

表示方法参数的注解,那么只能出现在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];
}

这是一个变长属性,分别是:

5.3 AnnotationDefault

表示注解方法的默认值信息,所以它也只能出现在method_info 的属性表attributes 中。

AnnotationDefault_attribute {
   u2 attribute_name_index;
   u4 attribute_length;
   element_value default_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

上一篇 下一篇

猜你喜欢

热点阅读