Jdk动态代理,CGlib动态代理都是通过生成字节码流并加载到Jvm中来实现生成代理类的。接下来介绍class文件的格式,希望能让大家对class字节码文件的认识更深入。
Class文件是一组以8位字节为基础单位的2进制流,各个数据项按顺序紧凑的排列在文件中,Class文件采用类似C语言的结构体的伪结构表示,在这种结构体中有两种数据类型,无符号数和表(table),无符号数属于基本数据类型u1、u2和u4类型分别代表占用1个字节、2个字节和4个字节的无符号数,无符号数可以用来描述数字、索引引用、数量或者按照UTF-8编码构成的字符串值(在Java中可以通过DataInput接口的readUnsignedByte、readUnsignedShort和readInt方法读取指定相应字节量的数据)。表类型(table)是由多个无符号数或其他表(table)作为数据项构成的嵌套结构,表结构类型通常以"_info"结尾。
Class文件伪结构表示:
ClassFile { // 4字节的数据,称为魔数,十六进制表示为:0xCAFEBABE,用来标识这是一个Java字节码文件 u4 magic; // 2字节,代表jdk小版本号 u2 minor_version; // 2字节,代表jdk主版本号,主要用来判断当前jdk是否能兼容当前Class文件 u2 major_version; // 2字节,代表常量池中数据项的个数 u2 constant_pool_count; // table类型,数据项个数为constant_pool_count-1,数据项索引从1-constant_pool_count-1,索引0保留 cp_info { u1 tag; u1 info[];} constant_pool[constant_pool_count-1]; u2 access_flags;// 2字节,访问标志 u2 this_class;// 2字节 u2 super_class;// 2字节 u2 interfaces_count;// 2字节 u2 interfaces[interfaces_count];// 2字节 u2 fields_count;// 2字节 field_info fields[fields_count];// u2 methods_count;// 2字节 method_info methods[methods_count];// u2 attributes_count;// 2字节 attribute_info attributes[attributes_count];// }
常量池constant_pool中的常量类型及类型结构如下:(tag值以注释形式标出了,解析时需要用到)
// 每种常量类型的具体数据个数如下CONSTANT_Class_info { u1 tag; // 7 u2 name_index;}CONSTANT_Fieldref_info { u1 tag; // 9 u2 class_index; u2 name_and_type_index;}CONSTANT_Methodref_info { u1 tag; // 10 u2 class_index; u2 name_and_type_index;}CONSTANT_InterfaceMethodref_info { u1 tag; // 11 u2 class_index; u2 name_and_type_index;}CONSTANT_String_info { u1 tag; // 8 u2 string_index;}CONSTANT_Integer_info { u1 tag; // 3 u4 bytes;}CONSTANT_Float_info { u1 tag; // 4 u4 bytes;}CONSTANT_Long_info { u1 tag; // 5 u4 high_bytes; u4 low_bytes;}CONSTANT_Double_info { u1 tag; // 6 u4 high_bytes; u4 low_bytes;}CONSTANT_NameAndType_info { u1 tag; // 12 u2 name_index; u2 descriptor_index;}CONSTANT_Utf8_info { u1 tag; // 1 u2 length; u1 bytes[length];}CONSTANT_MethodHandle_info { u1 tag; // 15 u1 reference_kind; u2 reference_index;}CONSTANT_MethodType_info { u1 tag; // 16 u2 descriptor_index;}CONSTANT_InvokeDynamic_info { u1 tag; // 18 u2 bootstrap_method_attr_index; u2 name_and_type_index;}
访问标识符:
下面我们通过一个简单的Class文件进行分析,通过实际动手解析加深我们对Class字节码文件格式的理解和认识。解析实例方法中的Code属性内容时需要了解一些Jvm中的指令,后续Jvm指令集整理好后贴出
源码:
package com.sourcecode.springboot.tomcatwar;public class ClassTest { private String s1 = "abc"; private static final String s2 = "edf"; public static void main(String[] args) {} public String print(String msg) { System.err.println(msg); return msg; }}
javap展示结果:(占用篇幅多,删掉了main方法和实例方法,保留了构造方法,可自行执行命令查看完整结果)
$ javap -p -v ClassTest.classpublic class com.sourcecode.springboot.tomcatwar.ClassTest minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPERConstant pool: #1 = Methodref #7.#30 // java/lang/Object."":()V #2 = String #31 // abc #3 = Fieldref #6.#32 // com/sourcecode/springboot/tomcatwar/ClassTest.s1:Ljava/lang/String; #4 = Fieldref #33.#34 // java/lang/System.err:Ljava/io/PrintStream; #5 = Methodref #35.#36 // java/io/PrintStream.println:(Ljava/lang/String;)V #6 = Class #37 // com/sourcecode/springboot/tomcatwar/ClassTest #7 = Class #38 // java/lang/Object #8 = Utf8 s1 #9 = Utf8 Ljava/lang/String; #10 = Utf8 s2 #11 = Utf8 ConstantValue #12 = String #39 // edf #13 = Utf8 #14 = Utf8 ()V #15 = Utf8 Code #16 = Utf8 LineNumberTable #17 = Utf8 LocalVariableTable #18 = Utf8 this #19 = Utf8 Lcom/sourcecode/springboot/tomcatwar/ClassTest; #20 = Utf8 main #21 = Utf8 ([Ljava/lang/String;)V #22 = Utf8 args #23 = Utf8 [Ljava/lang/String; #24 = Utf8 MethodParameters #25 = Utf8 print #26 = Utf8 (Ljava/lang/String;)Ljava/lang/String; #27 = Utf8 msg #28 = Utf8 SourceFile #29 = Utf8 ClassTest.java #30 = NameAndType #13:#14 // "":()V #31 = Utf8 abc #32 = NameAndType #8:#9 // s1:Ljava/lang/String; #33 = Class #40 // java/lang/System #34 = NameAndType #41:#42 // err:Ljava/io/PrintStream; #35 = Class #43 // java/io/PrintStream #36 = NameAndType #44:#45 // println:(Ljava/lang/String;)V #37 = Utf8 com/sourcecode/springboot/tomcatwar/ClassTest #38 = Utf8 java/lang/Object #39 = Utf8 edf #40 = Utf8 java/lang/System #41 = Utf8 err #42 = Utf8 Ljava/io/PrintStream; #43 = Utf8 java/io/PrintStream #44 = Utf8 println #45 = Utf8 (Ljava/lang/String;)V{ private java.lang.String s1; descriptor: Ljava/lang/String; flags: ACC_PRIVATE private static final java.lang.String s2; descriptor: Ljava/lang/String; flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL ConstantValue: String edf public com.sourcecode.springboot.tomcatwar.ClassTest(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: aload_0 5: ldc #2 // String abc 7: putfield #3 // Field s1:Ljava/lang/String; 10: return LineNumberTable: line 3: 0 line 4: 4 LocalVariableTable: Start Length Slot Name Signature 0 11 0 this Lcom/sourcecode/springboot/tomcatwar/ClassTest; }SourceFile: "ClassTest.java"
魔数、主次版本、常量池数据项个数解析:
常量池中常量数据项解析:
根据字节码文件第一行偏移量为OA处的十六进制为A=10,说明这个常量数据项的tag=10,找到对应的常量类型CONSTANT_Methodref_info,该方法引用类型的数据格式如下:
CONSTANT_Methodref_info {
u1 tag; // 10
u2 class_index;
u2 name_and_type_index;
}
可知后面两个字节0x0007 = 7代表class_index,说明该方法所在类的信息指向常量池索引为7的常量,再后面两个字节0x001E = 30代表name_and_type_index,说明该方法的名称及类型信息指向常量池中索引为30的常量。
按照上面解析常量池数据项的方式,将常量池中的所有数据项解析出来,发现解析出的信息和使用javap命令查看class文件展示的常量池信息一致:
访问标志解析:
this_class、super_class、interfaces_count、interfaces[]、fields_count数据项解析:
attribute_info数据格式(在字段、方法及Class属性中将会使用到):
attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length];}ConstantValue_attribute { u2 attribute_name_index; u4 attribute_length; u2 constantvalue_index;}
field_info:fields[fields_count]数据项解析:
根据上文分析可知,该Class文件有2个字段信息,因此后面有两个连续的field_info类型的数据,field_info的数据格式:
field_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count];}
字段数据项解析如下图:
methods_count、methods[]数据项解析:
method_info数据格式:
method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count];}
如果不是抽象方法时,方法中在编译后,在Class文件中的该方法信息的属性表中会存在Code属性,Code_attribute数据格式为:
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解析:
可以看到解析结果和通过javap展示的结果一致:
后面继续补充完善!!!
attributes_count数据项解析:
attributes[]数据项解析: