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