java class文件格式

本文讲解如何解析java class文件
以jdk8作为演示

1. class文件结构

u2、u4等代表占用字节数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ClassFile {
u4 magic; //魔数
u2 minor_version; //次版本号
u2 major_version; //主版本号
u2 constant_pool_count; //常量池中的项数
cp_info constant_pool[constant_pool_count-1]; //常量信息,数量为[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]; //类属性信息
}

本文中使用以下代码作为演示

1
2
3
4
5
6
7
8
9
10
package demo.jvm;

public class ClassFile {

long a;

public static void main(String[] args) {
System.out.println("Hello");
}
}

idea hexview插件显示对应的字节码文件(十六进制)

2. 魔数

前四个字节ca fe ba be代表魔数(Magic Number),标识这个文件是一个java类文件。

3. 版本号

第5和第6个字节代表次版本号,第7和第8个字节代表主版本号。
次版本号:00 00
主版本号:00 34,十进制为52
java版本从45开始,52代表java8

4. 常量池

4.1 constant_pool_count

9、10两个字节代表常量池中的项数(constant_pool_count),索引为0项的无效。
即索引大于0且小于constant_pool_count被认为是有效的
00 24:常量池项数, 十进制为36,即有35项常量

4.2 constant_pool

常量池标签

常量池标签结构


从第10个字节往后有35个常量信息。
常量信息的第一个字节对应常量池标签中的类型,后续占用的字节以常量池标签结构为准。
0a为第一个字节,十进制为10,10在常量标签中代表方法引用,方法引用的标签结构占用5个字节,从0a开始计算。
第一个常量:0a 00 06 00 16
#1 = Methodref #6.#22
Methodref的class_index引用了第6项常量信息
Methodref的name_and_type_index引用了第22项常量信息

通过javap反编译后

5.类访问标识


最后一个常量的后两个字节为访问标识。
当前类字节码中为00 21,值为多个相加。

6. 类名,父类名,接口名集合

6.1 类名

访问标识后两个字节为当前类全限定名。
00 05:引用了常量池中的第5项,即为demo/jvm/ClassFile

6.2 父类名

类名后两个字节为父类的全限定名
00 06:引用了常量池中的第6项,即为java/lang/Object

6.3 接口名集合

父类名称后两位为接口数量(interfaces_count)
00 00 当前类未实现任何接口,数量为0。
如果有,后续的2个字节代表一个接口信息,也是引用了常量池中常量项,
没有则不会有字节记录接口信息。

7. 字段

字段结构

1
2
3
4
5
6
7
field_info {
u2 access_flags; //访问标识
u2 name_index; //字段名称,指向constant_pool表中
u2 descriptor_index; //描述符,即字段类型,指向constant_pool表中
u2 attributes_count; //其它属性的数量
attribute_info attributes[attributes_count]; //其它属性值
}

字段访问标识

字段描述符

接口信息后两个字节为字段数量(fields_count)
00 01:有一个字段。
第一个字段字节码:00 00 00 07 00 08 00 00
00 00:访问标识,表示当前字段无任何访问标识(同类访问标识相同)
00 07:字段名称,引用了常量池中第7项,即a
00 08:字段描述符,引用了常量池中的第8项,即J,J在字段描述符中表示long类型
00 00:其他属性,无其他属性

8.方法

方法结构

1
2
3
4
5
6
7
method_info {
u2 access_flags; //访问标识
u2 name_index; //方法名称,指向constant_pool表中
u2 descriptor_index; //描述符,即参数和返回值。指向constant_pool表中
u2 attributes_count; //其它属性数量
attribute_info attributes[attributes_count]; 其他属性值
}

方法访问标识

方法描述符

1
2
3
4
5
6
7
( {ParameterDescriptor} ) ReturnDescriptor
ParameterDescriptor:字段类型
ReturnDescriptor:字段类型或V无返回值
例:
Object m(int i, double d, Thread t) {...}
is
(IDLjava/lang/Thread;)Ljava/lang/Object;

属性结构
以下只列出Code属性结构,其它可从属性文档中查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Code属性结构
Code_attribute {
u2 attribute_name_index; //属性名称,constant_pool中
u4 attribute_length; //属性长度,表示当前属性所占用的字符。不包含名称和长度
u2 max_stack; //操作数栈的最大深度
u2 max_locals; //局部变量表中最大槽数
u4 code_length; //字节码长度
u1 code[code_length]; //字节码指令,长度为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; //属性Code的属性数量
attribute_info attributes[attributes_count]; //属性信息
}

字段结束后的2个字节为方法数量(methods_count)
00 02:有两个方法
第一个方法字节码:00 01 00 09 00 0a 00 01 00 0b 00 00 00 2f 00 01 00 01 00 00 00 05 2a b7 00 01 b1 00 00 00 02 00 0c 00 00 00 06 00 01 00 00 00 03, 00 0d 00 00 00 0c 00 01 00 00 00 05 00 0e 00 0f 00 00

方法信息:
00 01:访问标识,public的
00 09:方法名称,引用常量池中第9项,即<init>构造方法
00 0a:方法描述符,十进制为10,引用常量池中第10项,即()V,无参无返回值
00 01:其他属性1个

属性信息:
00 0b:属性名称,十进制为11,引用了常量池中第7项,即Code
00 00 00 2f: 属性长度,十进制为47,后续的47个字节是当前属性内容
00 01:操作数栈的最大深度
00 01:局部变量表中最大槽数
00 00 00 05 :字节码长度
2a b7 00 01 b1:字节码指令
00 00:异常表中的条目数
00 02:属性Code的细节属性数量
后续的其它属性解析是一样的,对应属性结构即可

9.属性信息

0001 0014 0000 0002 0015
00 01:属性数量
00 14:属性名称,十进制为20,引用常量池中第20项,即SourceFile
00 00 00 02:属性长度,后续2个字节是当前属性的内容
00 15:源文件名称,十进制为21,引用常量池中第21项,即ClassFile.java

10.官方文档

class文件结构官方文档