我们都知道计算机在执行程序时,只能识别0和1格式的程序,但在Java虚拟机出现之后,便有了一种新的程序存储格式——字节码。也正因为实现了从机器码到字节码的转变,使得Java程序可以”Write Once, Run Anywhere”(一次编写,到处运行)。时至今日,出现了一大批基于虚拟机运行的语言,比如:Scala、Groovy等。
今天的学习结果就是记录一下Java程序在编译成字节码后的文件结构,也就是Class的文件结构。如果大家想要了解更多的相关介绍,可以阅读Java虚拟机规范。
1.Class类文件的结构
每个Class文件都是由8字节为单位的字节流组成。所有的16位、32位和64位数据都被构造成2个、4个和8个字节单位来表示。多字节数据项按照Big-Endian(以最高位字节放在地址最低位,最低位字节放在地址最高位的顺序存储)。
在Java虚拟机规范中使用类似于C语言结构体的伪结构来存储数据,这种伪结构有两种数据类型:无符号数和表。
无符号数属于基本数据类型,以u1、u2、u4、u8来分别表示1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串值。
表是由多个无符号数或者其他表作为数据项构成的复合数据类型。
Class类文件结构体
ClassFile {
u4 magic; //魔数 有固定值0xCAFEBABE
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 //属性表
}
下面介绍一下结构体中各项的具体表述意义:
magic:魔数,魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的 Class 文件。魔数值固定为 0xCAFEBABE,不会改变。
minor_version major_version :副版本号和主版本号,minor_version 和 major_version 的值分别表示 Class 文件的副、主版本。它们共同构成了 Class 文件的格式版本号。
constant_pool_count :常量池计数器,constant_pool_count的值等于constant_pool表中的成员数加1。constant_pool 表的索引值只有在大于 0 且小于 constant_pool_count 时才会被认为是有效的。
constant_pool:常量池,constant_pool 是一种表结构,它包含 Class 文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量常量池中的每一项都具备相同的格式特征——第一个字节作为类型标记用于识别项是哪种类型的常量,称为“tag byte”。常量池的索引范围是1至constant_pool_count−1。
access_flags :
访问标志,access_flags 是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性,如:public、abstract等。
this_class :类索引,this_class 的值必须是对 constant_pool 表中项目的一个有效索引值。constant_pool 表在这个索引处的项必须为 CONSTANT_Class_info 类型常量,表示这个 Class 文件所定义的类或接口。
super_class: 父类索引,对于类来说,super_class 的值必须为 0 或者是对 constant_pool 表中项目的一个有效索引值。如果它的值不为 0,那 constant_pool 表在这个索引处的项必须为 CONSTANT_Class_info 类型常量,表示这个 Class 文件所定义的类的直接父类。
interfaces_count :接口计数器,interfaces_count 的值表示当前类或接口的直接父接口数量。
interfaces:接口表,interfaces[]数组中的每个成员的值必须是一个对constant_pool 表中项目的一个有效索引值,它的长度为 interfaces_count。每个成员 interfaces[i] 必须为 CONSTANT_Class_info 类型常量,其中 0 ≤ i < interfaces_count。在 interfaces[]数组中,成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)一样,即 interfaces[0]对应的是源代码中最左边的接口。
fields_count :字段计数器,fields_count 的值表示当前 Class 文件 fields数组的成员个数。fields数组中每一项都是一个 field_info 结构的数据项,它用于表示该类或接口声明的类字段或者实例字段。
fields:字段表,fields数组中的每个成员都必须是一个 fields_info 结构的数据项,用于表示当前类或接口中某个字段的完整描述。fields数组描述当前类或接口
声明的所有字段,但不包括从父类或父接口继承的部分。
methods_count :方法计数器,methods_count 的值表示当前 Class 文件methods数组的成员个数。
methods:方法表,methods[]数组中的每个成员都必须是一个 method_info 结构的数据项,用于表示当前类或接口中某个方法的完整描述。
attributes_count :属性计数器,attributes_count 的值表示当前 Class 文件 attributes 表的成员个数。
attributes:属性表,attributes 表的每个项的值必须是 attribute_info 结构。在Java虚拟机规范里,Class 文件结构中的 attributes 表的项包括下列定义的属性:
InnerClasses、EnclosingMethod、Synthetic 、Signature 、SourceFile, SourceDebugExtension、Deprecated、RuntimeVisibleAnnotations、RuntimeInvisibleAnnotations以及BootstrapMethods属性。对于支持 Class 文件格式版本号为 49.0 或更高的 Java 虚拟机实现,必须正确识别并读取 attributes 表中的 Signature、RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations。对于支持 Class 文件格式版本号为 51.0 或更高的 Java 虚拟机实现,必须正确识别并读取 attributes 表中的BootstrapMethods属性。
上述所有数据项的介绍都来自与Java虚拟机规范,更具体的描述可以参考Java虚拟机规范。