Notes - Java Class format

深入理解Class文件格式

信息描述规则

> 数据类型描述规则

原始数据类型对应的字符串描述

数据类型 描述
byte B
char C
double D
float F
int I
long J
short S
boolean Z

引用数据类型格式为 "LClassName". ex: Ljava/lang/String;. 全路径名的 “.” 由 “/“ 替代,并且最后必须要带分号(;)
数组也是一种引用类型,用 "[其他类型描述名" 表示,比如一个int数组 [I,二维int数组 [[I

> 成员变量描述规则

> 成员函数描述规则


> access_flags介绍

Class的access_flags取值

|标志名|取值|说明|
|-|-|
|ACC_PUBLIC|0x0001|public类型|
|ACC_FINAL|0x0010|final类型|
|ACC_SUPER|0x0020|用于invokespecial指令|
|ACC_INTERFACE|0x0200|表明这个类是一个interface|
|ACC_ABSTRACT|0x0400|abstract类型|
|ACC_SYNTHETIC|0x1000|表明该类由编译器根据情况生成的,源码里无法显示定义这样的类|
|ACC_ANNOTATION|0x2000|注解类型|
|ACC_ENUM|0x4000|枚举类型|

Field的access_flags取值

标志名 取值 说明
ACC_PUBLIC 0x0001 public类型
ACC_PRIVATE 0x0002 private类型
ACC_PROTECTED 0x0004 protected类型
ACC_STATIC 0x0008 static类型
ACC_FINAL 0x0010 final类型
ACC_VOLATILE 0x0040 volatile类型
ACC_TRANSIENT 0x0080 transient类型,说明该成员不能被串行化
ACC_SYNTHETIC 0x1000 表明该成员由编译器根据情况生成,源码里无法显示定义这样的成员
ACC_ENUM 0x4000 枚举类型

Method的access_flags取值

标志名 取值 说明
ACC_PUBLIC 0x0001 public类型
ACC_PRIVATE 0x0002 private类型
ACC_PROTECTED 0x0004 protected类型
ACC_STATIC 0x0008 static类型
ACC_FINAL 0x0010 final类型
ACC_SYNCHRONIZED 0x0020 synchronized函数
ACC_BRIDGE 0x0040 桥接方法,由编译器根据情况生成
ACC_VARARGS 0x0080 可变参数个数的函数
ACC_NATIVE 0x0100 native函数
ACC_ABSTRACT 0x0400 抽象函数
ACC_STRICT 0x0800 strictfp类型(strict float point,精确浮点)
ACC_SYNTHETIC 0x1000 表明该成员由编译器根据情况生成的,源码里无法直接定义这样的成员(通常称之为合成函数。另外,内部类访问外部类的私有成员时,在class文件中也会生成一个ACC_SYNTHETIC修饰的函数)

属性名称和作用

名称 说明
“ConstantValue” 该属性只出现于field_info中,用于描述一个常量成员域(long、float、double、int、char、short、byte、boolean、String等)的值
“Code” 该属性只出现于method_info中,用于描述一个函数(非native和abstract的函数)的内容:即源码中该函数内容编译后得到的虚拟机指令,try/catch语句对应的异常处理表等
“Exceptions” 当一个函数抛出异常(Exception)或错误(Error)时,这个函数的method_info将保存此属性
“SourceFile” 此属性位于ClassFile的属性集合中,它包含一个指向Utf8常量项的索引,包含此Class对应的源码文件名
“LocalVariableTable” 属性还可以包含属性。比如”LocalVariableTable”属性就是包含在”Code”属性中的,用来描述一个函数的本地变量相关信息。比如这个变量的名字、这个变量在源码哪一行定义的

结论:
1、属性的类型由其名字来描述。比如”Code”、”SourceFile”等。
2、不同类型的属性可能出现在ClassFile中不同的成员里,比如”Code”属性只能出现在method_info中。虚拟机在解析Class文件的时候是需要校验很多内容的,比如abstract的函数或native的函数就不能携带”Code”属性。
3、属性也可以包含子属性,比如”Code”属性能包含”LocalVariableTable”属性。


Structs

ClassFile Struct

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];
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];
}

cp_info struct

1
2
3
4
cp_info {  				// u1表示该域对应一个字节长度,u表示unsigned无符号的
u1 tag; // 每一个cp_info的第一个字节表明该常量项的类型
u1 info[]; // 常量项的具体内容
}

常量项

常量项Utf8、Class等对应的数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
CONSTANT_Utf8_info {
u1 tag;
u2 length; // 表示bytes数组的长度
u1 bytes[length]; // 存储了字符串的内容
}

CONSTANT_Class_info {
u1 tag;
u2 name_index; // 索引,指向常量池中一个元素类型为Utf8_info的项
}

CONSTANT_String_info { // 代表一个字符串
u1 tag;
u2 string_index; // 索引,指向常量池中一个元素类型为Utf8_info的项
}

CONSTANT_MethodType_info {
u1 tag;
u2 descriptor_index; // 索引,指向常量池中一个元素类型为Utf8_info的项
}

CONSTANT_Fieldref_info {
u1 tag;
u2 class_index; // 索引,指向常量池中一个元素类型为Class_info的项
u2 name_and_type_index;
}

CONSTANT_Methodref_info {
u1 tag;
u2 class_index; // 索引,指向常量池中一个元素类型为Class_info的项
u2 name_and_type_index;
}

CONSTANT_NameAndType_info {
u1 tag;
u2 name_index; // 索引,指向常量池中一个元素类型为Utf8_info的项
u2 descriptor_index; // 索引,指向常量池中一个元素类型为Utf8_info的项
}

CONSTANT_InterfaceMethodref_info {
u1 tag;
u2 class_index; // 索引,指向常量池中一个元素类型为Class_info的项
u2 name_and_type_index;
}

常量项Long、Integer等对应的数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CONSTANT_Long_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}

CONSTANT_Integer_info {
u1 tag;
u4 bytes;
}

CONSTANT_Double_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}

CONSTANT_Float_info {
u1 tag;
u4 bytes;
}

常量池

field_infomethod_info数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute attributes[attributes_count];
}

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

access_flags: 访问标志,成员变量和成员函数的访问标志略有不同

name_index: 指向成员变量或成员函数名字的Utf8_info常量项

descriptor_index: 指向Utf8_info常量项,其内容分别是描述成员变量的FieldDescriptor和描述成员函数的MethodDescriptor

attributes: 属性信息,成员域和成员函数都包含若干属性

属性

1
2
3
4
5
attribute_info {
u2 attribute_name_index; // 属性名称,指向常量项中的Utf8_info
u4 attribute_length; // 该属性具体内容的长度,即下面info数组的长度
u1 info[attrubute_length]; // 属性具体内容
}

Code属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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];
}

attribute_name_index: 指向内容为”Code”的Utf8_info常量项

attribute_length: 表示接下来内容的长度

max_stack: JVM执行一个指令的时候,该指令的操作数存储在一个名叫”操作数栈(operand stack)“的地方,每一个操作数占一个或两个(long、double类型的操作数)栈项。
stack就是一块只能先入后出的内存。max_stack用于说明这个函数在执行过程中,需要最深多少栈空间(也就是多少栈项)。

max_locals: 表示该函数包括最多几个局部变量

code_lengthcode: 函数对应的指令内容也就是这个函数的源码经过编译转换后得到的Java指令码存储在code数组中,其长度由code_length表示

exception_table_lengthexception_table: 一个函数可以包含多个try/catch语句,一个try/catch语句对应exception_table数组中的一项

pc(program counter): JVM执行的时候,会维护一个变量来指向当前要执行的指令,这个变量就叫pc

start_pc: 描述try/catch语句从哪条指令开始。注意,这个table中的各个pc变量的取值必须位于代表整个函数内容的Java字节码**code[code_length]**数组中

end_pc: 表示这个try语句到哪条指令结束。注意,只包括try语句,不包括catch

handler_pc: 表示catch语句的内容从哪条指令开始

catch_type: 表示catch语句中截获的Exception或Error的名字,指向Utf8_info常量项。如果catch_type取值为0,则表示它是final{}语句块

Code_attribute还能包含其他属性:

LineNumberTable: 用于调试,比如指明哪条指令。对应源码哪一行。

LocalVariableTable: 用于调试,调试时可以用于计算本地变量的值。

LineNumberTable属性

1
2
3
4
5
6
7
8
9
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];
}

start_pc: 指向Code_attribute中code数组某处指令

line_number: 说明start_pc位于源码哪一行。注意,多个line_number_table元素可以指向同一行代码。因为一行Java代码很可能被编译成多条指令

LocalVariableTable属性

1
2
3
4
5
6
7
8
9
10
11
12
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];
}

start_pclength: 决定了一个局部变量在code数组中的有效范围

name_index: 此局部变量的名字,指向Utf8_info常量项

descriptor_index: 此局部变量的类型,也指向Utf8_info常量项,其内容是FieldDescriptor字符串描述

...: …