Java 诞生之时有句著名的宣传口号“Write Once, Run Anywhere.”。但是,Java 语言本身不具备跨平台的能力,而是 JVM 提供了跨平台的能力。
事实上,不仅 Java 可以运行在 JVM 上,现在已经出现了其他的语言可以运行在 JVM 上,如 Clojure、Scala 等。也就是说,JVM 并不和包括 Java 在内的任何语言绑定,只与“Class 文件”这种特定的二进制文件格式相关联,Class 文件包含了 Java 虚拟机指令集和符号表以及若干其他辅助信息。这篇文章就主要介绍存储字节码的 Class 文件。
任何一个 Class 文件都对应着唯一一个类或接口的定义信息,但是,类或接口的定义信息并非一定要在类文件中。
Class 文件是一组以 8 位为基础单位的字节流(二进制流),每个数据项严格按照顺序节凑排列在 Class 文件中,中间没有任何分隔符。多字节的数据项按照大端序存储(高字节存储在最低地址位)。
基本概念
JVM 规范规定,Class 文件采用一种类似于 C 语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表。
- 无符号数是基本的数据类型,以 u1、u2、u3、u4 分别代表 1 个字节、2 个字节、3 个字节、 4 个字节。无符号数可以用来描述数字、索引引用、数量值或者按照 UTF-8 编码构成字符串值。
- 表由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯地以"_info"结尾。表用于描述有层次关系的复合结构的数据,这个 Class 文件本质上就是一张表。
文件结构
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];
}
每个 Class 文件就对应这样一个 ClassFile 的结构。
无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的数据项的形式,这是称这一序列连续的某一个类型的数据为某一类型集合。如 fields_count
来描述 field_info
的数量(这些 field_info
就构成一个集合)。
ClassFile 中每个项目解释如下表(attributes_count 和 attributes[] 比较复杂,这里暂不介绍,我会在文章末尾提供参考资料):
item | 含义 |
---|---|
magic | 魔数,它的唯一作用时确定这个文件能否为一个能被虚拟机接受的 Class 文件。许多文件标准中都使用魔数来进行身份标识。Class 文件的魔数值为:0xCAFEBABE |
minor_version | 这个类文件的次版本号。 |
major_version | 这个类文件的主版本号。 |
constant_pool_count | 常量池容器计数值,等于常量池(constant_pool) 中实体数量加 1 。常量池的索引只有在比零大,比该值小才认为是有效的。 |
constant_pool[] | 常量池中的结构表示字符串常量、类名字、接口名字、字段(field name)、其他常量(指向 ClassFile 中的其他结构、子结构)。 |
access_flag | 访问标志。用于识别一些类或接口层次的访问信息,包括:这个 Class 是类还是接口;是否定义为 public;是否定义为 abstract 类型;如果是类的话,是否被声明为 final 等。 |
this_class | 用于确定这个类的全限定名。 |
super_class | 用于确定这个类的父类的全限定名。 |
interfaces_count | 用于标识类实现了几个接口。 |
interfaces[] | 类实现了哪些接口,按 implements 后的顺序显示在这个集合中。 |
fields_count | 用于描述接口或者类中声明的变量的数量,字段包括类级变量以及实例级变量。 |
fields[] | 用于描述接口或者类中声明的变量,字段包括类级变量以及实例级变量。描述信息包括:字段的作用域(public、private、protected)、实例变量还是类变量(static)、可变性(final)、并发可见性(volatile)、字段数据类型、字段名称、可否被序列化。除字段数据类型、字段名称需要引用常量池中常量来描述外,其余均用标志位来表示。 |
methods_count | 接口或类中方法的数量。 |
methods[] | 描述接口或类中的方法。 |
Class 的文件格式大体上就是这样,还有大量细节没有描述,如常量池中各种常量项的结构、访问标志具体值与名称、字段描述符、方法描述符、属性表等。由于这些都是规范,这里不再写了,想更近一步了解的话,可以阅读 Java 虚拟机规范。
参考:
The Java Virtual Machine Specification, Java SE 8 Edition
《深入理解Java虚拟机》