JVM专题——类文件结构
本文部分内容节选自Java Guide和《深入理解Java虚拟机》, Java Guide地址: https://javaguide.cn/java/jvm/class-file-structure.htmlClass文件是一组以8个字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符, 这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,
本文部分内容节选自Java Guide和《深入理解Java虚拟机》, Java Guide地址: https://javaguide.cn/java/jvm/class-file-structure.html
🚀 基础(上) → 🚀 基础(中) → 🚀基础(下) → 🤩集合(上) → 🤩集合(下) → 🤗JVM专题1 → 🤗JVM专题2 → 🤗JVM专题3 → 🤗JVM专题4 →😋JUC专题1 → 😋JUC专题2
Class 类文件结构
Class文件是一组以8个字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符, 这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据, 没有空隙存在。当遇到需要占用8个字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8个字节进行存储。
ClassFile
结构如下
ClassFile {
u4 magic; //Class 文件的标志
u2 minor_version;//Class 的小版本号
u2 major_version;//Class 的大版本号
u2 constant_pool_count;//常量池的数量
cp_info constant_pool[constant_pool_count-1];//常量池
u2 access_flags;//Class 的访问标记
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];//属性表集合
}
魔数与Class文件版本号
每个Class文件的头四个字节被称为 魔数 , 它用来判断这个文件是否是一个能够被Java虚拟机接收的Class文件. Java虚拟机规定魔数必须是 0xCAFEBABE , 否则虚拟机会拒绝加载这个Class文件
紧接着魔数的四个字节是 Class文件的版本号 , 第5和第6字节是 次版本号 , 第7和第8字节是 主版本号 . 高版本的JDK能够向下兼容低版本的Class文件, 但是低版本的JDK不能向上兼容高版本的Class文件
常量池
紧接着魔数和Class文件版本号之后的是 常量池入口 . 常量池可以比喻为Class文件中的资源仓库, 它是Class文件结构中与其他项目关联最多的数据, 同时也是占用Class文件空间最大的数据项目之一, 另外它还是Class文件中第一个出现的表类型数据项目
u2 constant_pool_count;//常量池的数量
cp_info constant_pool[constant_pool_count-1];//常量池
常量池的数量是 constant_pool_count - 1
(常量池计数器是从1开始计数的, 常量池索引为0表示 “不引用任何一个常量池项”)
常量池中主要存放两大类常量: 字面量 和 符号引用 . 字面量比较接近于 Java 语言中的常量概念, 而符号引用属于编译原理的概念, 主要包括以下几类变量
- 被模块导入或者开放的包
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
- 方法句柄和方法类型
- 动态调用点和动态常量
常量池中每一个常量都是一个表, 到JDK13版本目前有17个不同类型的常量
类型 | 标志 | 描述 |
---|---|---|
CONSTANT_Utf8_info | 1 | UTF-8编码的字符串 |
CONSTANT_Integer_info | 3 | 整型字面量 |
CONSTANT_Float_info | 4 | 浮点型字面量 |
CONSTANT_Long_info | 5 | 长整型字面量 |
CONSTANT_Double_info | 6 | 双精度浮点型字面量 |
CONSTANT_Class_info | 7 | 类或接口的符号引用 |
CONSTANT_String_info | 8 | 字符串类型字面量 |
CONSTANT_Fieldref_info | 9 | 字段的符号引用 |
CONSTANT_Methodref_info | 10 | 类中方法的符号引用 |
CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符号引用 |
CONSTANT_NameAndType_info | 12 | 字段或方法的部分符号引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANT_MethodType_info | 16 | 表示方法类型 |
CONSTANT_Dynamic_info | 17 | 表示一个动态计算常量 |
CONSTANT_InvokeDynamic_info | 18 | 表示一个动态方法调用点 |
CONSTANT_Module_info | 19 | 表示一个模块 |
CONSTANT_Package_info | 20 | 表示一个模块中开放或者导出的包 |
访问标志
在常量池结束之后, 紧接着的2个字节代表 访问标志 , 用于识别一些类或者接口层次的访问信息, 包括: 这个Class是类还是接口; 是否为 public 或者abstract; 如果是类的话, 是否被声明为 final
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否为public |
ACC_FINAL | 0x0010 | 是否为final |
ACC_SUPER | 0x0020 | 是否使用invokespecial字节码指令的新语义 |
ACC_INTERFACE | 0x0200 | 是否是一个接口 |
ACC_ABSTRACT | 0x0400 | 是否为abstract |
ACC_SYNTHETIC | 0x1000 | 标识这个类并非由用户代码产生的 |
ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
ACC_ENUM | 0x4000 | 标识这是一个枚举 |
ACC_MODULE | 0x8000 | 标识这是一个模块 |
类索引, 父类索引和接口索引集合
类索引, 父类索引和接口索引都按顺序排列在访问标志之后, 类索引和父类索引用两个u2类型的索引值表示, 它们各自指向一个类型为CONSTANT_Class_info的类描述符常量, 通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在 CONSTANT_Utf8_info类型的常量中的全限定名字符串
u2 this_class;//当前类
u2 super_class;//父类
u2 interfaces_count;//接口数量
u2 interfaces[interfaces_count];//一个类可以实现多个接口
类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,由于 Java 语言的单继承,所以父类索引只有一个,除了 java.lang.Object
之外,所有的 Java 类都有父类,因此除了 java.lang.Object
外,所有 Java 类的父类索引都不为 0。
字段表集合
字段表用于描述接口或类中声明的变量. 字段包括类级变量以及实例变量, 但不包括在方法内部声明的局部变量
u2 fields_count;//字段数量
field_info fields[fields_count];//一个类会可以有个字段
字段表结构
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 atributes_count;
attribute_info attributes[attributes_count];
}
- access_flags : 字段的作用域 (
public
,private
,protected
修饰符), 是实例变量还是类变量, 可否被序列化, 可变性(final
修饰 ) , 可见性(volatile
) - name_index : 对常量池的引用, 表示的字段的名称
- descriptor_index : 对常量池的引用, 标识字段和方法的描述符
- attributes_count : 一个字段还会拥有一些额外的属性, attributes_count 存放属性的个数
- attributes[attributes_count] : 存放具体属性具体内容
上述这些信息中, 各个修饰符都是布尔值, 要么有某个修饰符, 要么没有, 很适合使用标志位来表示. 而字段叫什么名字, 字段被定义为什么数据类型这些都是无法固定的, 只能引用常量池中常量来描述
方法表集合
Class文件存储格式中对于方法的描述和对字段的描述几乎是一模一样的, 方法表的结构如同字段表一样, 依次包括了访问标志, 名称索引, 描述符索引, 属性表集合等
u2 methods_count;//方法数量
method_info methods[methods_count];//一个类可以有个多个方法
方法表结构
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
因为 volatile
和 transient
修饰符不能修饰方法, 所以方法表的访问标志中没有这两个对应的标志, 但是增加了 synchronized
, native
, abstract
等关键字修饰方法, 所以也就多了这些关键字对应的标志
属性表集合
u2 attributes_count;//此类的属性表中的属性数
attribute_info attributes[attributes_count];//属性表集合
与 Class 文件中其他的数据项目要求严格的顺序, 长度, 内容不同, 属性表集合的限制稍微宽松一点, 不再要求各个属性表具有严格的顺序, 并且Java虚拟机规范允许只要不与已有属性名重复, 任何人实现的编译器都可以向属性表中写入自己定义的属性信息, Java虚拟机运行时会忽略掉它不认识的属性
更多推荐
所有评论(0)