美文网首页
JVM_字节码的属性

JVM_字节码的属性

作者: wo883721 | 来源:发表于2021-12-12 16:07 被阅读0次

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

    attribute_info {
       u2 attribute_name_index;
       u4 attribute_length;
       u1 info[attribute_length];
    }
    
    • attribute_name_index : 属性名对应的常量池中 CONSTANT_Utf8_info 类型常量的有效索引,值就是属性名,通过属性名区分不同属性。
    • attribute_length : 属性数据的长度,不包括 attribute_name_indexattribute_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 个属性分为三个部分:

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

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

    • number_of_classesclasses[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_countparameters[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 的值就是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];
    }
    

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

    • line_number_table_lengthline_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_lengthlocal_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_lengthlocal_variable_type_table[local_variable_type_table_length] : 记录局部变量的泛型签名和作用范围。
      • start_pc : 开始的指令地址。
      • length : 作用范围长度。
      • name_index : 局部变量的名字。
      • signature_index : 局部变量泛型签名。
      • index : 局部变量在局部变量表的索引。

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

    记录方法相关数据:

    • max_stack : 当前方法执行时最大操作数栈深度,和 max_locals 一起用来计算方法对应虚拟机栈的栈帧大小。
    • max_locals : 当前方法局部变量表的大小,和 max_locals 一起用来计算方法对应虚拟机栈的栈帧大小。
    • code_lengthcode[code_length]: 记录方法编译后的指令数据。
    • exception_table_lengthexception_table[exception_table_length]: 方法异常处理器。

      这个指定是方法中 try {} catch(){} 语句,而不是方法声明中 throw 的异常。
      - start_pc : 开始指令地址,包括它。
      - end_pc : 结束指令地址,不包括它。
      - handler_pc : 跳转执行异常的指令地址。
      - catch_type : CONSTANT_Class_info 类型常量池有效索引,表示这个异常的类型。

    • attributes_countattributes[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 那个格式作用

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

    • offset_delta : 表示当前这个栈映射帧的增量偏移量,用于计算每个帧在运行时的实际字节码偏移量。

      使用时帧的字节偏移量计算方法为: 前一帧的字节码偏移量(Bytecode Offset)加上offset_delta的值再加1,如果前一个帧是方法的初始帧(Initial Frame),那这时候字节码偏移量就是offset_delta

    • locals[] : 记录方法运行到这里,本地变量表中变量情况,类型是 verification_type_info
    • stack[] : 这个和方法当前操作数栈没有关系,主要是和异常有关,就是当前捕捉到的异常。

    那为什么要分为 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];
    }
    

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

    • 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_infoCONSTANT_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 接口实例的 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
    
    • #21 就是 bootstrap_method_ref,是一个 CONSTANT_MethodHandle_info 类型,就是LambdaMetafactory 的静态方法 metafactory,返回值是 CallSite 类型。

      java 会通过字节码生成一个实现 Runnable 接口的类,并从这个自生成类中查找 run 方法的引用,由这个方法引用创建一个CallSite 类型实例,这样就可以通过方法引用直接调用这个自生成类的 run 方法。

    • #22,#23#22 都是这个引导方法的参数列表,类型分别是 CONSTANT_MethodHandle_info,CONSTANT_MethodType_infoCONSTANT_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 类型,分别是

    • RuntimeVisibleAnnotationsRuntimeInvisibleAnnotations : 类,字段和方法的注解。
      • Visible 表示运行期可见,即可以通过反射获取;Invisible 表示运行期不可见,即不能通过反射获取。
      • 这里的类指的是 java 的四种类型,即 class,interface,enum@interface;都可以使用注解。
    • RuntimeVisibleParameterAnnotationsRuntimeInvisibleParameterAnnotations: 方法参数的注解。

      方法参数注解和普通注解的格式有点区别。

    • AnnotationDefault : 注解默认值。

      它主要是记录注解方法的默认值。

    • RuntimeVisibleTypeAnnotationsRuntimeInvisibleTypeAnnotations : 类型注解,这个是 1.8 提供的新功能。

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

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

    • attribute_name_index : 属性名对应的常量池中 CONSTANT_Utf8_info 类型常量的有效索引,值就是 RuntimeVisibleAnnotations或者RuntimeInvisibleAnnotations
    • attribute_length : 表示当前这个属性去除 attribute_name_indexattribute_length6 个字节之后的剩余总字节数。
    • 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,booleanString 这九种常量。

    • 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=100name="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 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];
    }
    

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

    • attribute_name_index : 属性名对应的常量池中 CONSTANT_Utf8_info 类型常量的有效索引,值就是 RuntimeVisibleParameterAnnotations或者RuntimeInvisibleParameterAnnotations
    • attribute_length : 表示当前这个属性去除 attribute_name_indexattribute_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_indexattribute_length6 个字节之后的剩余总字节数。
    • 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

    相关文章

      网友评论

          本文标题:JVM_字节码的属性

          本文链接:https://www.haomeiwen.com/subject/resrfrtx.html