转载

class字节码,这次我算看透你了!

java代码是通过java编译器编译成class文件,然后由jvm加载执行的,jvm屏蔽了底层平台系统执行细节,所以可以做到Compile Once,Run Anywhere。

class字节码,这次我算看透你了!

编译后的class文件,是一个二进制流文件,例如下面的类:

public class ServiceResult<T> {

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

    private int code;
    private String message;
    private T data;

    public ServiceResult(T data) {
        this.code = SUCCESS_CODE;
        this.message = SUCCESS_MESSAGE;
        this.data = data;
    }

    public ServiceResult(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public boolean isSuccess() {
        return code == SUCCESS_CODE;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    public T getData() {
        return data;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("ServiceResult{");
        sb.append("code=").append(code);
        sb.append(", message='").append(message).append('/'');
        sb.append(", data=").append(data);
        sb.append('}');
        return sb.toString();
    }
}
复制代码

编译后得到的class,以16进制格式打开如下:

class字节码,这次我算看透你了!

注:class文件以字节(8比特)为单位,用u1,u2,u4,u8分别表示1个字节,2个字节,4个字节,8个字节的无符号数,采用Big-edian形式,即高位字节在前。

Class结构

二进制class文件如果用类c语言结构体的形式来描述其逻辑结构,则如下图所示:

class字节码,这次我算看透你了!

从图中可知,class文件主要包含magic,minor version,major version,constant pool,access flags,this_class,super class,interfaces,fields,methods,attributes 11个部分,每个部分之间紧凑的拼接在一起,没有分界符分割,下面分别介绍每个结构。

在开始介绍各个结构之前,需要说明本文以jvm1.8为准

本文有些长,这里排版看起来更舒服些

1. magic

魔数:占4个字节的无符号数,固定为0xCAFEBABE,用来标识改文件是一个class文件

2. minor version

次版本号:占两个字节的无符号数,范围0~65535,与major version一起表示当前class文件的版本,jvm可以向前兼容之前的版本,但不能向后兼容,即jdk7的虚拟机不能运行jdk8编译的class

3. major version

主版本号:占两个字节的无符号数,jdk1.1使用的主版本号是45,以后每个大版本加1,如jdk1.8为52

4. constant pool

常量池:常量池是class中十分重要的一部分,它可不是只保存着类中定义的常量而已,还保存着class文件中的各种元数据,包括一些字符串,类名,接口名,字段名,方法名等等……,它的作用就是被引用,常量池部分首先有两个字节u2记录它包含的常量个数。

PS1:常量池就是一系列常量的数组,它的下标是从1开始的,即有效大小是constant_pool_count-1,第0项是无效的,有些结构可以用索引0来表示没有对常量的引用

PS2:常量池的设计有效的减小的class文件的大小,想想那些重复使用的类名称,字符串现在只需保留一份,并且引用的地方只需要用u2保存它在常量池中的索引就可以了

​ 因为每个常量都有一种具体的类型来代表不同的含义,光知道常量的个数还没办法解析出具体的常量项来,所以定义每个常量的第一个字节u1表示该常量的类型tag,然后就可以根据该类型常量的存储结构来解析了。

​ 常量的tag有CONSTANT_Utf8,CONSTANT_Integer,CONSTANT_Float,CONSTANT_Long,CONSTANT_Double,CONSTANT_Class,CONSTANT_String,CONSTANT_Fieldref,CONSTANT_Methodref,CONSTANT_InterfaceMethodref,CONSTANT_NameAndType,CONSTANT_MethodHandle,CONSTANT_MethodType,CONSTANT_InvokeDynamic等14种,下面对每种类型结构(类型+“_info”)作下介绍:

4.1 CONSTANT_Utf8_info

常量池中最基本的常量,用来保存一个utf8编码字符串,如常量字符串,类名,字段名,方法名等的值都是一个对它的引用(索引)

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}
复制代码

tag=1,length表示字符串字节长度,如length=20,则表示接下来20个bytes是一个utf8编码的字符串。

这里补充两点:

  • java使用的是可变utf8编码:ASCII 字符(' /u0001 ' ~ ' /u007F ',即1~127)用1个字节表示,null(' /u0000 ')和 ' /u0080 ' 到 ' /u07FF '之间的字符用2个字节表示, ' /u0800 ' 到 ' /uFFFF '之间的字符用3个字节表示。

    逆向来看就是如果读到一个字节最高位是0,则是一个单字节字符。

    读到一个字节最高3位是 110 则是一个双字节字符,紧接着还要再读1个字节。

    读到一个字节最高4位是 1110 ,则是一个三字节字符,紧接着还要再读2个字节。

    关于如何解码可以查看官方文档,在java中,我们只需要使用 new String(bytes, StandardCharset.UTF8) 即可得到解码字符串

  • length使用了u2(0-65535)来表示,则其表示的字符串最大长度为65535

4.2 CONSTANT_Integer_info

CONSTANT_Integer_info {
    u1 tag;
    u4 bytes;
}
复制代码

int,tag=3,接下来4个字节表示该int的值。关于CONSTANT_Integer补充以下几点:

  • big-endian,字节高位在前,下文同理

    如果自己解析则要像下面这样:

    int value = 0;
    byte[] data = new byte[4];
    is.read(data);
    value = (value | (((int) data[0]) & 0xff)) << Byte.SIZE * 3;
    value = (value | (((int) data[1]) & 0xff)) << Byte.SIZE * 2;
    value = (value | (((int) data[2]) & 0xff)) << Byte.SIZE;
    value = (value | (((int) data[3]) & 0xff));
    复制代码

    我们可以使用DataInputStream的readInt()方法读取一个int值。

  • java中 short , char , byte , boolean 使用int来表示,boolean数组则用byte数组来表示(1个byte表示1个boolean元素)

4.3 CONSTANT_Float_info

CONSTANT_Float_info {
    u1 tag;
    u4 bytes;
}
复制代码

float浮点数,tag=4,接下来4个字节表示它的值,采用 IEEE 754标准定义。可以使用DataInputStream的readFloat()方法读取一个float值。

4.4 CONSTANT_Long_info

CONSTANT_Long_info {
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;
}
复制代码

tag=5,长整数,long和double在class中用两个部分(高位4字节,地位4字节)保存。可以使用DataInputStream的readLong()方法读取一个float值。

4.5 CONSTANT_Double_info

CONSTANT_Double_info {
    u1 tag;
    u4 high_bytes;
    u4 low_bytes;
}
复制代码

tag=6,双精度浮点数,采用 IEEE 754标准定义。存储同CONSTANT_Long一样。

4.6 CONSTANT_Class_info

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}
复制代码

tag=7,表示一个类或接口,注意不是field的类型或method的参数类型、返回值类型。name_index是常量池索引,该索引处常量肯定是一个 CONSTANT_Utf8_info

4.7 CONSTANT_String_info

CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}
复制代码

tag=8,表示一个常量字符串,string_index是常量池索引,该索引处常量肯定是一个 CONSTANT_Utf8_info ,存储着该字符串的值

4.8 CONSTANT_Fieldref_info

CONSTANT_Fieldref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}
复制代码

tag=9,表示一个引用field信息,包括静态field和实例field。

class_index是常量池中一个CONSTANT_Class_info类型常量(类/接口)索引,表示field所属类。name_and_type_index是常量池中一个CONSTANT_NameAndType_info(见下文)类型常量索引,表示field的名称和类型。

关于field引用解释一下,包括下面的method,接口method引用同理:

code
field_info

4.9 CONSTANT_Methodref_info

CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}
复制代码

tag=10,表示一个引用method信息,包括静态method和实例method。

class_index是常量池中一个CONSTANT_Class_info类型常量(这里只能是类)索引,表示method所属类。name_and_type_index是常量池中一个CONSTANT_NameAndType_info类型常量索引,表示method的名称和参数,返回值信息。

4.10 CONSTANT_InterfaceMethodref_info

CONSTANT_InterfaceMethodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}
复制代码

tag=11,表示一个接口method信息。

class_index是常量池中一个CONSTANT_Class_info类型常量(这里只能是接口)索引,表示method所属接口。name_and_type_index同CONSTANT_Methodref_info。

4.11 CONSTANT_NameAndType_info

CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index;
    u2 descriptor_index;
}
复制代码

tag=12,存储field或method的名称,类型等信息,可以看出它又是两个引用。name_index指向一个CONSTANT_Utf8_info,表示字段或方法的 非全限定名称 。descriptor_index也指向一个CONSTANT_Utf8_info,表示该字段/方法的描述信息。

Descriptor

descriptor用一个字符串CONSTANT_Utf8_info保存。

  • 字段描述符( FieldType ),FieldType可以是基本类型: B(byte) C(char) D(double) F(float) I(int) J(long) S(short) Z(boolean) ,对象类型:L+全限定类名,数组类型:[+元素类型

    int a; // I
    Integer b; //Ljava/lang/Integer
    double[] c; //[D
    double[][] d; //[[D
    Object[] e; //[Ljava/lang/Object
    Object[][][] f; //[[[Ljava/lang/Object
    复制代码
  • 方法描述符( MethodDescriptor ),MethodDescriptor格式为 (参数类型)返回类型

    /**
     * 描述符:(IDLjava/lang/Thread;)Ljava/lang/Object;
     */
    Object m(int i, double d, Thread t) {...}
    复制代码

4.12 CONSTANT_MethodHandle_info

CONSTANT_MethodHandle_info {
    u1 tag;
    u1 reference_kind;
    u2 reference_index;
}
复制代码

tag=15,方法句柄,比如获取一个类静态字段,实例字段,调用一个方法,构造器等都会转化成一个句柄引用。

  • reference_kind

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

    f: field,m: method,:实例构造器

  • reference_index

    • 对于Kind=1,2,3,4,reference_index引用一个CONSTANT_Fieldref_info
    • 对于Kind=5,6,7,8,reference_index引用一个CONSTANT_Methodref_info
    • 对于Kind=9,reference_index引用一个CONSTANT_InterfaceMethodref_info

4.13 CONSTANT_MethodType_info

CONSTANT_MethodType_info {
    u1 tag;
    u2 descriptor_index;
}

复制代码

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

4.14 CONSTANT_InvokeDynamic_info

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

复制代码

tag=18,invokedynamic动态调用指令引用信息。

  • bootstrap_method_attr_index,BootstrapMethods属性中bootstrap_methods[]数组的索引,每个引导方法引用了CONSTANT_MethodHandle_info
  • name_and_type_index,引用一个CONSTANT_NameAndType_info常量

5. access flags

access flags表示类,接口,字段,方法的访问控制和修饰信息。

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

其中大部分都能见名知意,补充以下几点:

  • ACC_SUPER:用于invokespecial指令而需要特殊处理的父类方法
  • ACC_BRIDGE:桥方法标志,有该标志的方法上同时有ACC_SYNTHETIC标志
  • ACC_STRICT:strictfp,strict float point,方法使用 FP-strict 浮点格式
  • ACC_SYNTHETIC:标志是由编译器生成的,源码中并没有

6. this class

当前类或接口,指向一个CONSTANT_Class_info常量,可以从中解析当前类的全限定名称。包名层次用 / 分割,而不是 . ,如 java/lang/Object

7. super class

当前类的直接父类索引,指向一个CONSTANT_Class_info常量,当没有直接父类时super_class=0

8. interfaces

首先用u2表明当前类或接口的直接父接口数量n。紧接着n个u2组成的数组即是这些父接口在常量池的索引,类型是CONSTANT_Class_info,按声明顺序从左至右。

9. fields

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

复制代码

field_info保存当前类的fields信息。很简单,其中大部分前面都讲过了,关于attributes放在下文第11节专门讲解。需要注意的是fields只包含当前类的字段,如A的内部类B的字段c,则是在类A$B中

10. methods

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

复制代码

保存当前类的方法信息,同field_info

11. attributes

属性表:属性存在与 ClassFile , field_info , method_info 中,此外Code属性中又包含嵌套属性信息,属性用来描述指令码,异常,注解,泛型等信息,JLS8预定义了23种属性,每种属性结构不同(变长),但可以抽象成下面通用结构。

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

复制代码

attribute_name_index:是该属性名称在常量池中的索引,通过该名称才可以判定当前属性属于具体哪一种,如“Code”表示当前是一个Code_attribute

attribute_length:表示接下来多少字节是该属性的内容信息,java允许自定义新的属性,如果jvm不认识,则按通用结构直接读取attribute_length个字节。

23种属性按作用可以分为3组:

  • 被jvm翻译使用:ConstantValue,Code,StackMapTable,Exceptions,BootstrapMethods
  • 被java类库解析使用:InnerClasses,EnclosingMethod,Synthetic,Signature,RuntimeVisibleAnnotations/RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations/RuntimeInvisibleParameterAnnotations,RuntimeVisibleTypeAnnotations/RuntimeInvisibleTypeAnnotations,AnnotationDefault,MethodParameters
  • 既不要求jvm解析,也不要求java类库解析,用于调试工具等场景:SourceFile,SourceDebugExtension,LineNumberTable,LocalVariableTable,LocalVariableTypeTable,Deprecated

注:后面我会介绍如何解析class,所以本文只对每个属性的结构和作用做一个简单介绍

11.1 ConstantValue

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

复制代码

存在于field_info,代表一个常量值,如 private final int x = 5 中的 5 。attribute_name_index引用的值是“ConstantValue”,attribute_length固定为2,接下来两个字节的constantvalue_index是该常量值在常量池中的索引,是CONSTANT_Long,CONSTANT_Float,CONSTANT_Double,CONSTANT_Integer,CONSTANT_String的一种。

11.2 Code

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

复制代码

描述方法体编译后的字节码指令。前面讲过描述方法的 method_info 结构,而方法的方法体信息就存在它的属性表中code属性内。如果是抽象方法,那就没有这个属性。

前面在讲属性通用结构 attribute_info 的时候已经讲过 attribute_name_index , attribute_length ,它是每个属性都有的,下文就不在说明了,只对其他部分介绍。

  • max_stack , 操作数栈的最大深度,用来分配栈的大小

  • max_locals, 方法栈帧中局部变量表最大容量,存储局部变量,方法参数,异常参数等。以slot为单位,32bit以内的变量用分配1个slot,大于32bit,如long、double分配2个slot,注意对象存的是引用。另外指出一点,对于实例方法,默认会传入this对象指针,所以这时的max_locals最小为1。

  • code[code_length],存储字节码指令列表,每条字节码指令是一个byte,这样8bit最多可以表示256条不同指令,需要指出的是这个字节流数组存的不全是指令,有的指令还有对应的操作数,跳过相应n个字节的操作数再往后才是下一条指令,详细内容我会在另外的文章中演示。

  • exception_table[exception_table_length],方法异常表,注意不是方法声明抛出的异常,而是显示try-catch的异常,每个catch的异常时exception_table的一项。

    • catch_type,捕获的异常类型,指向一个CONSTANT_Class_info常量
    • start_pc,字节码指令相对方法开始的偏移量,相当于code[code_length]中的索引
    • end_pc, 字节码指令相对方法开始的偏移量,相当于code[code_length]中的索引
    • handler_pc,字节码指令相对方法开始的偏移量,相当于code[code_length]中的索引

    这几项表示的意思是:如果在[start_pc, end_pc)区间发生了catch_type类型或其子类的异常(catch_type=0表示捕获任意异常),则跳转至handler_pc处的指令继续执行。

    补充三点:

    1)关于finaly块中的指令采用的方式是在每个代码分支中冗余一份。

    2)关于未显示捕获的异常则通过 athrow 指令继续抛出

    3)虽然指令长度code_length是u4,但start_pc,end_pc,handler_pc都只有2个字节的无符号数u2,最大表示范围只有65535,因此方法最多只能有65535条指令(每条指令都不带操作数的情况下)

  • attributes[attributes_count],嵌套属性列表

11.3 StackMapTable

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

复制代码

上面讲到Code_attribute中也可以包含属性表,StackMapTable就位于Code属性的属性表中,它是为了在jvm字节码验证阶段做类型推导验证而添加的

11.4 Exceptions

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

复制代码

表示通过 throws 声明的可能抛出的异常,结构很简单exception_index_table每一项u2指向一个CONSTANT_Class_info常量

11.5 BootstrapMethods

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

复制代码

位于ClassFile中,保存 invokedynamic 指令引用的引导方法

  • bootstrap_method_ref,引用一个一个 CONSTANT_MethodHandle_info 常量,此时该MethodHandle的reference_kind 必定为REF_invokeStatic或REF_newInvokeSpecial
  • num_bootstrap_arguments,引导方法参数列表,数组中每一项是一个 CONSTANT_String_info , CONSTANT_Class_info , CONSTANT_Integer_info , CONSTANT_Long_info , CONSTANT_Float_info , CONSTANT_Double_info , CONSTANT_MethodHandle_info , or CONSTANT_MethodType_info 引用

11.6 InnerClasses

InnerClasses_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 number_of_classes;
    {   u2 inner_class_info_index;
        u2 outer_class_info_index;
        u2 inner_name_index;
        u2 inner_class_access_flags;
    } classes[number_of_classes];
}
复制代码

记录内部类信息,classes就是当前类的内部类列表,其中inner_class_info_index,outer_class_info_index指向CONSTANT_Class型常量,分别代表内部类和外部类信息引用,inner_name_index是内部类名称的引用(CONSTANT_Utf8_info),等于0则代表是匿名内部类,inner_class_access_flags是内部类访问标志,同access_flags

11.7 EnclosingMethod

EnclosingMethod_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 class_index;
    u2 method_index;
}
复制代码

位于ClassFile结构中,存储局部类或匿名类信息。

  • class_index,对直接包含它的类的引用,引用一个CONSTANT_Class_info常量,代表包含当前类声明的最内层类
  • method_index,引用一个CONSTANT_NameAndType_info常量,表示直接包含该局部类、匿名类的方法名称和类型

11.8 Synthetic

Synthetic_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
}

复制代码

标记是否类、方法、字段为编译器生成,与ACC_SYNTHETIC同义,attribute_length=0,存在该属性则表示true。

11.9 Signature

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

复制代码

存在于类,方法,字段的属性表中,用于存储类,方法,字段的泛型信息(类型变量Type Variables,参数化类型Parameterized Types)。

关于泛型可以参考这里

  • signature_index,引用一个CONSTANT_Utf8_info常量,表示签名

11.10 RuntimeVisibleAnnotations

RuntimeVisibleAnnotations_attribute {
    u2         attribute_name_index;
    u4         attribute_length;
    u2         num_annotations;
    annotation annotations[num_annotations];
}

复制代码

存在于类,方法,字段,存储运行时可见的(RetentionPolicy.RUNTIME)注解信息,可以被反射API获取到,关于注解可以参考这里

annotation结构存储了注解名称,元素值对的信息,具体可以参考官方文档,或者我后面class解析的文章

11.11 RuntimeInvisibleAnnotations

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

复制代码

与RuntimeVisibleAnnotations结构相同,但不可见,即不能被反射API获取到,目前jvm忽略此属性

11.12 RuntimeVisibleParameterAnnotations

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

复制代码

存在于method_info的属性表中,存储运行时可见的方法参数注解信息,与RuntimeVisibleAnnotations对比发现,RuntimeVisibleParameterAnnotations存储的是方法的参数列表上每个参数的注解(相当与一组RuntimeVisibleParameterAnnotations),顺序与方法描述符中参数顺序一致

11.13 RuntimeInvisibleParameterAnnotations

RuntimeInvisibleParameterAnnotations_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 num_parameters;
    {   u2         num_annotations;
        annotation annotations[num_annotations];
    } parameter_annotations[num_parameters];
}
复制代码

不想再啰嗦了

11.14 RuntimeVisibleTypeAnnotations

RuntimeVisibleTypeAnnotations_attribute {
    u2              attribute_name_index;
    u4              attribute_length;
    u2              num_annotations;
    type_annotation annotations[num_annotations];
}
复制代码

存在于class_file,method_info,field_info,code的属性表中,java8新增。JLS8新增两种ElementType(ElementType.TYPE_PARAMETER, ElementType.TYPE_USE),相应用来描述的注解属性也做了相应的改的,就有了该属性,type_annotation存储着注解信息及其作用对象。

11.15 RuntimeInvisibleTypeAnnotations

RuntimeInvisibleTypeAnnotations_attribute {
    u2              attribute_name_index;
    u4              attribute_length;
    u2              num_annotations;
    type_annotation annotations[num_annotations];
}
复制代码

略。。。

11.16 AnnotationDefault

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

复制代码

存在于 method_info 属性表 ,记录注解元素的默认值

11.17 MethodParameters

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

复制代码

存在于 method_info 属性表 ,记录方法参数信息,name_index形参名称,access_flags有ACC_FINAL,ACC_SYNTHETIC,ACC_MANDATED

11.18 SourceFile

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

复制代码

class_file属性表中,记录生成该的文件名,异常堆栈可能显示此信息,一般与类名相同,但内部类不是。这是一个可选属性,意味着不强制编译器生成此信息。

11.19 SourceDebugExtension

SourceDebugExtension_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 debug_extension[attribute_length];
}
复制代码

存在于class结构中,可选,保存非java语言的扩展调试信息。 debug_extension 数组是指向CONSTAN_Utf8_info的索引

11.20 LineNumberTable

LineNumberTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 line_number_table_length;
    {   u2 start_pc;
        u2 line_number;	
    } line_number_table[line_number_table_length];
}
复制代码

code的属性表中,存储源码行号与字节码偏移量(方法第几条指令)之间映射关系,start_pc字节码偏移量,line_number源码行号,可选。

问题:在错误堆栈中如何打印出出错的源码行号的?如何支持在源码上断点调试?

11.21 LocalVariableTable

LocalVariableTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_table_length;
    {   u2 start_pc;
        u2 length;
        u2 name_index;
        u2 descriptor_index;
        u2 index;
    } local_variable_table[local_variable_table_length];
}
复制代码

code的属性表中,存储栈帧中局部变量表的变量与源码中定义的变量的映射,可以在解析code属性时关联到局部变量表变量在源码中的变量名等,可选。

11.22 LocalVariableTypeTable

LocalVariableTypeTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_type_table_length;
    {   u2 start_pc;
        u2 length;
        u2 name_index;
        u2 signature_index;
        u2 index;
    } local_variable_type_table[local_variable_type_table_length];
}
复制代码

code的属性表中,与LocalVariableTable相似,signature_index也引用一个 CONSTANT_Utf8_info 常量,对应含有泛型的变量会同时存储到LocalVariableTable和LocalVariableTypeTable中个一份

11.23 Deprecated

Deprecated_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
}
复制代码

类、方法、字段过期标记,没有额外信息,attribute_length=0,如果出现该属性则说明加了@deprecated注解

完! 如果觉得写的还可以,给个赞鼓励一下吧!

下期预告:动手编写一个解析class(字节码)文件的程序

关注微信号,更多精彩等着你

class字节码,这次我算看透你了!
原文  https://juejin.im/post/5d884c766fb9a06ae76444dc
正文到此结束
Loading...