class文件结构概述
class文件是一种8位字节的二进制流文件, 各个数据项按顺序紧密的从前向后排列, 相邻的项之间没有间隙, 这样可以使得class文件非常紧凑, 体积轻巧, 可以被JVM快速的加载至内存, 并且占据较少的内存空间。 我们的Java源文件, 在被编译之后, 每个类(或者接口)都单独占据一个class文件, 并且类中的所有信息都会在class文件中有相应的描述。
class文件中的每个数据项都有它的固定长度, 数据项的不同长度分别用u1,u2,u4,u8表示,长度分别是byte、short、int、long。
class文件中存在以下数据项(该图表参考自《深入Java虚拟机》):
类型 | 名称 | 数量 |
---|---|---|
u4 | magic | 1 |
u2 | minor_version | 1 |
u2 | major_version | 1 |
u2 | constant_pool_count | 1 |
cp_info | constant_pool | constant_pool_count - 1 |
u2 | access_flags | 1 |
u2 | this_class | 1 |
u2 | super_class | 1 |
u2 | interfaces_count | 1 |
u2 | interfaces | interfaces_count |
u2 | fields_count | 1 |
field_info | fields | fields_count |
u2 | methods_count | 1 |
method_info | methods | methods_count |
u2 | attribute_count | 1 |
attribute_info | attributes | attributes_count |
class文件每个区域的说明:
magic
和version
: magic 也即魔数(固定值0xCAFEBABE)占用4字节, class 文件版本号占用4字节, 不同版本的 javac 编译器编译的 class 文件, 版本号可能不同;常量池数量
constant_pool_count
, 等于实际常量池中项目的数量+1,因为常量池计数器是从1开始计数的,将第0项常量空出来是有特殊考虑的,索引值为0代表“不引用任何一个常量池项”。常量池(
constant_pool
)存储的内容主要包括 符号引用 和 字面量;access_flag
, 在常量池之后的两个字节, 这个标志用于识别一些类或接口层次的访问信息this_class
/super_class
/interfaces
: 类索引(this_class
)和父类索引(super_class
)都是一个 u2 类型的数据,而接口索引集合(interfaces
)则是一组 u2 类型的数据集合, Class 文件中由这三项数据来确定这个类的继承关系;field_info
字段表method_info
方法表attribute_info
属性表方法字节码…
class文件常量池
常量池(constant_pool
)存储的内容主要包括 符号引用 和 字面量:
- 字面量: 主要包括字符串常量和 final 常量值;
- 符号引用: 包括类继承的超类, 接口的全限定名, 及描述符(包括 fields 的名称和描述符, methods 的名称及描述符)
- 类和接口的全限定名: 例如一个类的权限定名是
org/kshan/corej/TestClass
; - 字段的名称和描述符:
- 字段名称: 当类被加载后的链接阶段, 这些符号引用被替换为直接引用;
- 字段描述符: 用来描述字段的类型比如二维数组
int [][]
被记录为[[I
,String[]
被记录为[Ljava/lang/String
;
- 方法的名称和描述符:
- 方法名称: 当类被加载后的链接阶段, 这些符号引用被替换为直接引用;
- 方法描述符: 用来描述方法的形参/返回值, 例如方法
int getIndex(String name,char[] tgc,int start,int end,char target)
的描述符为(Ljava/lang/String[CIIC) I
;
- 类和接口的全限定名: 例如一个类的权限定名是
class 文件中的很多其他部分都是对常量池中的数据项的引用,比如后面要讲到的 this_class
, super_class
, field_info
, attribute_info
等,另外字节码指令中也存在对常量池的引用,这个对常量池的引用当做字节码指令的一个操作数。此外,常量池中各个项也会相互引用。
注意不要与JVM内存模型中的”运行时常量池”混淆, Class文件中常量池主要存储了字面量以及符号引用,其中
字面量主要包括字符串,final 常量的值或者某个属性的初始值等等,
下图参考自: 《Java虚拟机原理图解》 Class文件中的常量池详解 @ref
实例分析 class 文件常量池
Java测试类:
public class CJEntry extends CJBaseClass implements Serializable { |
编译后使用 javap 分析 class 文件: javac org/mk/corej/CJBaseClass.java && javap -v org.mk.corej.CJBaseClass
, 只截取输出的”Constant pool” 部分:
#1 = Methodref #5.#23 // org/kshan/corej/CJBaseClass."<init>":()V ## 构造方法的符号引用 |
局部变量表、操作数栈
@todo