classFile的文件结构:
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
写一段代码:
package jvm; public class ClassFile { }
然后 用查看二进制文件的工具:
CA FE BA BE 00 00 00 34 00 10 0A 00 03 00 0D 07 00 0E 07 00 0F 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 01 00 04 74 68 69 73 01 00 0F 4C 6A 76 6D 2F 43 6C 61 73 73 46 69 6C 65 3B 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00 0E 43 6C 61 73 73 46 69 6C 65 2E 6A 61 76 61 0C 00 04 00 05 01 00 0D 6A 76 6D 2F 43 6C 61 73 73 46 69 6C 65 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 00 21 00 02 00 03 00 00 00 00 00 01 00 01 00 04 00 05 00 01 00 06 00 00 00 2F 00 01 00 01 00 00 00 05 2A B7 00 01 B1 00 00 00 02 00 07 00 00 00 06 00 01 00 00 00 03 00 08 00 00 00 0C 00 01 00 00 00 05 00 09 00 0A 00 00 00 01 00 0B 00 00 00 02 00 0C
开头四个字节是CAFEBABE,这个是魔数,用于标识这个文件能被jvm识别。接着2个字节是次版本号,后续0x0034表示主版本号,52代表jdk8。(jdk1用45表示,后面每增加一个版本,就加1)
接着是0x0010=16,0用于保留,1 - 15表示有15个常量。接着:就是各个常量的解析了。每个常量的大小是不定的。
常量池的解析:
首先给出常量池中的tag类型:
Table 4.3. Constant pool tags 常量类型 常量值 Constant Type Value CONSTANT_Class 7 CONSTANT_Fieldref 9 CONSTANT_Methodref 10 CONSTANT_InterfaceMethodref 11 CONSTANT_String 8 CONSTANT_Integer 3 CONSTANT_Float 4 CONSTANT_Long 5 CONSTANT_Double 6 CONSTANT_NameAndType 12 CONSTANT_Utf8 1 CONSTANT_MethodHandle 15 CONSTANT_MethodType 16 CONSTANT_InvokeDynamic 18
第一个常量: 然后就到了0x0A = 10,查找上述的表格为:CONSTANT_Methodref
CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
其中第一个已经被用了,然后就是两个字节来表示class_index:对应的值为:0x0003,这个是表示常量池中的第3个变量。然后是0x00 0D表示常量池的第13个常量。
第二个常量: 然后就是一个字节:表示tag: 0x07。找到对应的CONSTANT_Class
CONSTANT_Class_info { u1 tag; u2 name_index; }
然后是两个字节的name_index:
0x00 0E,表示第14个常量。
第三个常量: 先是tag,占了一个字节 0x07,两个字节的name_index => 即0x00 0F,表示第15个常量的位置
第四个常量:先是tag0x01,CONSTANT_Utf8,结构为:
CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }
两个字节表示长度: 0x00 06,表示这个字符常量占六个字节:即: 3C 69 6E 69 74 3E
用16进制转字符:表示 <init>
第五个常量:显示tag=0x01,表示CONSTANT_Utf8,接着两个字节为:0x00 03,表示这个字符占三个字节:
28 29 56 表示: ()V
第六个常量:tag=1,长度为4:43 6F 64 65 ==> Code
第七个常量:tag=1,长度等于15:4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 ==》 LineNumberTable
第8个常量:tag =1,长度等于18:4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 - LocalVariableTable
第9个常量:tag =1,长度等于4,74 68 69 73 =》 this
第10个常量:tag = 1,长度:00 0F等于15,4C 6A 76 6D 2F 43 6C 61 73 73 46 69 6C 65 3B ==》 Ljvm/ClassFile;
第11个常量:tag = 1,长度: 00 0A 等于10, 53 6F 75 72 63 65 46 69 6C 65 =》 SourceFile
第12个常量: tag = 1,长度: 00 0E 等于14,43 6C 61 73 73 46 69 6C 65 2E 6A 61 76 61 ==》 ClassFile.java
第13个常量:tag = 0x0C,等于12,CONSTANT_NameAndType
CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; }
接着两个字节是: 00 04 表示第四个,
00 05,第五个常量。
第14个常量:tag =1 , 00 0D,等于13,6A 76 6D 2F 43 6C 61 73 73 46 69 6C 65 ==》 jvm/ClassFile
第15个常量: tag =1 ,00 10 等于16,6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 ==》 java/lang/Object
到此为止,所有的常量都解析完了。
接下来两个字节就表示访问标记:00 21 表示。类的访问标致表:
Table 4.1-A. Class access and property modifiers Flag Name Value Interpretation ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package. ACC_FINAL 0x0010 Declared final; no subclasses allowed. ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the invokespecial instruction. ACC_INTERFACE 0x0200 Is an interface, not a class. ACC_ABSTRACT 0x0400 Declared abstract; must not be instantiated. ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code. ACC_ANNOTATION 0x2000 Declared as an annotation type. ACC_ENUM 0x4000 Declared as an enum type.
可以看出这里有两个属性: ACC_PUBLIC ACC_SUPER 表示公有的,当由invokespecial指令调用时,要特别对待超类方法。
接下来两个字节表示:当前类名的索引:00 02表示常量池第二个,查看为:jvm/ClassFile
接下来两个字节表示:父类类名的索引:00 03表示常量池第三个,查看为:java/lang/Object
接下来两个字节表示:接口的数量,0000,表示没有实现接口。如果这个为0,那么接下来的接口interface_info,就不存在了。
接下来两个字节表示: 成员表里的数量,0000,表示没有成员变量。那么接着的field_info也不存在了。
接下来两个字节表示,方法的个数,0001表示有一个方法,接下来时对应method_info的信息。
method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
接下来两个字节表示方法的访问标志:方法访问标致表如下:
Table 4.6-A. Method access and property flags Flag Name Value Interpretation ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package. ACC_PRIVATE 0x0002 Declared private; accessible only within the defining class.ACC_PROTECTED 0x0004 Declared protected; may be accessed within subclasses. ACC_STATIC 0x0008 Declared static. ACC_FINAL 0x0010 Declared final; must not be overridden (§5.4.5). ACC_SYNCHRONIZED 0x0020 Declared synchronized; invocation is wrapped by a monitor use. ACC_BRIDGE 0x0040 A bridge method, generated by the compiler. ACC_VARARGS 0x0080 Declared with variable number of arguments. ACC_NATIVE 0x0100 Declared native; implemented in a language other than Java. ACC_ABSTRACT 0x0400 Declared abstract; no implementation is provided. ACC_STRICT 0x0800 Declared strictfp; floating-point mode is FP-strict. ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code.
00 01 表示:ACC_PUBLIC 表示公有的访问权限。
接下来是名字的索引: 00 04表示常量池里的第四个常量。即<init>
接下来是描述符的索引: 00 05 表示常量池第五个常量:()V这个表示空参数,空返回值
然后两个字节是属性的个数:0001表示有一个属性:属性表如下:
attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; }
接下来两个字节0006表示,属性名的索引是常量池的第6个常量,为Code
接下来四个字节表示属性长度:00 00 00 2F = 47
Co的结构为:
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]; }
接下47个字节就是Code里面的内容:
00 01 00 01 00 00 00 05 2A B7 00 01 B1 00 00 00 02 00 07 00 00 00 06 00 01 00 00 00 03 00 08 00 00 00 0C 00 01 00 00 00 05 00 09 00 0A 00 00
接下来是两个字节到max_stack=0x0001表示最大的栈深度为1
接下来表示本地变量:max_locals,表示有一个本地变量,这里明明没有本地变量的,为什么是1呢?其实是java中,每个方法都会有this这个本地变量。
接下来是四个字节表示存放code信息的数组长度:00000005,表示五个字节:即: 2A B7 00 01 B1
接下来两个字节表示异常表的长度:0000表示0,没有。
接下两个字段是属性的个数:0002表示有两个属性。
然后是两个字节的属性索引位置:0007表示常量池第7个常量,即: LineNumberTable,这个是记录源码行号的表。
然后是四个字节的属性长度:00 00 00 06表示属性长度为6,即:00 01 00 00 00 03
然后两给字节,表示第二个变量的索引位置:0008,即LocalVariableTable,用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,非运行时必须属性。
接下来四个字节:00 00 00 0C表示属性的长度为12,即:00 01 00 00 00 05 00 09 00 0A 00 00
至次,Code属性已经完结。
接下来两个属性表示:属性的个数: 0001,表示有一个属性,然后就是两个字节表示属性名在常量池中的索引位置:00 0B = 11,即SourceFile
接着四个字节表示这个属性的长度: 00 00 00 02 = 2,表示属性长度为2,即:00 0C
到此,字节码全部解析完成。
同样可以通过jclasslib插件来查看字节码的信息。可以在idea中安装这个插件,然后点开编译出来的.class文件,然后edit,有个show bytecode with jclasslib的按钮。
也可以通过javap -verbose ClassFile.class 可以反编译出对应的字节码文件。
public class jvm.ClassFile minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #3.#13 // java/lang/Object."<init>":()V #2 = Class #14 // jvm/ClassFile #3 = Class #15 // java/lang/Object #4 = Utf8 <init> #5 = Utf8 ()V #6 = Utf8 Code #7 = Utf8 LineNumberTable #8 = Utf8 LocalVariableTable #9 = Utf8 this #10 = Utf8 Ljvm/ClassFile; #11 = Utf8 SourceFile #12 = Utf8 ClassFile.java #13 = NameAndType #4:#5 // "<init>":()V #14 = Utf8 jvm/ClassFile #15 = Utf8 java/lang/Object { public jvm.ClassFile(); 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 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Ljvm/ClassFile; }