先写一段代码然后编译成class,直接对照16进制码阅读:
1 package test;
2 public class test {
3 private int m;
4
5 public int inc() {
6 return m + 1;
7 }
8 }
用winhex打开编译出的class后可以看到
CLASS文件的总体格式如下结构
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];
}来自 <https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1>
我们一点一点解读:
1. 开头4字节是javaclass标志性的magicnumber,CAFEBABE
2. 第5和6字节代表次版本号,一般都是0000
3. 第7-8字节表示的是主板本号,值为0x34即52,表示编译器版本是jdk8
4. 8-9字节是constant_pool入口,0x13即constant_pool_count表示了常量池中包含常量的数目是18=constant_pool_count-1
5. 接下来是每个常量池项的具体信息,常量池主要存放两大类常量:Literal 和 Symbolic Reference,Literal表示一些字符串常量以及声明为final的常量等,Symblic Reference则表示类和接口名、字段名、方法名等。
偏移为0xA位置的值为0xA,通过查表(1)可得这是一个CONSTANT_Methodref(字段),字段结构如下:
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
tag之后是class_index值为0x0004,class_index 项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Class_info结构,表示一个类或接口,当前字段或方法是这个类或接口的成员,值得注意的是如果一个 CONSTANT_Methodref_info 结构的方法名以“<”('\u003c')开头,说明这个方法名是特殊的,即这个方法是实例初始化方法。接着就是name_and_type_index,值为0x000f,name_and_type_index 项的值必须是对常量池的有效索引,常量池在该索引处的项必须是 CONSTANT_NameAndType_info结构,它表示当前字段或方法的名字和描述符。
偏移为0xF位置的值为0x9,是一个CONSTANT_Fieldref和CONSTANT_Methodref有一样的结构,详细说明参见:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1
偏移为0x14位置的值为0x7是一个CONSTANT_Class,我们跳过,偏移为0x17位置的值为0x7也是一个CONSTANT_Class,并且他就是最开始CONSTANT_Methodref第二个字段所指向的。
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
表(1)
来自 <https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1>
当然大可不必这么麻烦,javap可以轻松输出这些分析javap -verbose test.class:
从这里可以看出java所有类会继承java.lang.object,编译器会为没有显示构造函数的类做出一个默认的构造函数出来,在创建对象时,先调用父类默认构造函数对对象进行初始化。()V应该就是void的意思,I即int。
6. 接下来是access_flags位于0x9B,可以看到constant_pool占了很大一部分空间,access_flags值为0x0021,从表(2)看出这是一个public类,然后还有ACC_SUPER属性,在 JDK 1.0.2 之后编译出的 Class 文件,都带有 ACC_SUPER 标志用以和以前版本区分。这里说明一下invokespecial指令,每个类都至少会有一个<init>()方法,这些方法通常是用invokespecial调用。
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.
表(2)Class access and property modifiers
来自 <https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1>
7. 然后是this_class,值为0x0003是对constant_pool表中项目的一个有效索引值,从字面意思就是当前类了。
8. 之后是super_class,值为0x0004,即java.lang.object,如果 Class 文件的 super_class 的值为 0,那这个 Class 文件只可能是定义的是 java.lang.Object 类,只有它是唯一没有父类的类。
9. Interface_count表示当前类或接口的直接父接口数量,值为0即没有。
10. interfaces[]字段直接就没有了,可以看出class文件的紧凑。
11. 位于0xA2处的0x0001是field_count,表示当前 Class 文件 fields[]数组的成员个数。
12. 之后就是fieldtable的一个项了,filedtable项的结构为:
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}来自 <https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1>
Fileld_info值依次为0x0002 0x0005 0x0006 分别表示 private m int,随后的attributes_count为0,attribute项用来表示标识符的修饰,例如如果定义private static int m;则这里就会有constantvalue项。
13. 紧随其后的0xAC位置表示method_count,值为0x0002,接下来的0x0001 0x0007 0x0008分别表示public <init> void,这个初始化函数有attribute,attribute结构为:
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}来自 <https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7>
attribute_name_index值为0x0009,在constant_pool中索引为code,此属性为code描述符即java代码编译器编译成的字节码指令,之后的length为0x001D。
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];
}来自 <https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3>
随后的max_stack和max_locals都为1,max_stack代表了操作数栈深度的最大值,max_local代表了局部变量表所需空间,单位为slot,长度不超过32位的数据类型占一个slot,64位的数据如double和long需要2个slot。java编译器会根据变量的作用域来分配slot给各个变量使用。
Code_length为5,紧接着的便是java指令字节码,0x2AB7000AB1对应指令为,即加载参数、初始化、返回。等等哪里来的参数呢?实际上就是this啦。
接下来是异常处理,也是有点复杂,就跳过了,直接进入下一个方法入口位于0xD9处,分析同上。
直到0x106,两个方法结束。
14. 接着一个属性从0x106开始,name_index=0xD,为sourcefile,其格式为:
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}来自 <https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3>
对应的值为test.java即文件名。