作为类(或者接口) [1] 信息的载体,每个class文件都完整地定义了一个类。为了使Java程序可以“编写一次,处处运行”,Java虚拟机规范对class文件格式进行了严格的规定。Java虚拟机规范(和本书)中所指的class文件,并非特指位于磁盘中的.class文件,而是泛指任何格式符合规范的class数据。
构成class文件的基本数据单位是字节,可以把整个class文件当成一个字节流来处理。
稍大一些的数据由连续多个字节构成,这些数据在class文件中以大端(big-endian)方式存储。
为了描述class文件格式,Java虚拟机规范定义了u1、u2和u4三种数据类型来表示1、2和4字节无符号整数.
相同类型的多条数据一般按表(table)的形式存储在class文件中。表由表头和表项(item)构成,表头是u2或u4整数。假设表头是n,后面就紧跟着n个表项数据。
Java虚拟机规范使用一种类似C语言的结构体语法来描述class文件格式。整个class文件被描述为一个ClassFile结构,代码如下:
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];
}
以为ClassFileTest.class例
package jvmgo.book.ch03;
public class ClassFileTest {
public static final boolean FLAG = true;
public static final byte BYTE = 123;
public static final char X = 'X';
public static final short SHORT = 12345;
public static final int INT = 123456789;
public static final long LONG = 12345678901L;
public static final float PI = 3.14F;
public static final double E = 2.71828D;
public ClassFileTest() {
}
public static void main(String[] args) throws RuntimeException {
System.out.println("Hello, World!");
}
}
魔数
很多文件格式都会规定满足该格式的文件必须以某几个固定字节开头,这几个字节主要起标识作用,叫作魔数(magic number)。class文件的魔数
是“0xCAFEBABE”。
Java虚拟机规范规定,如果加载的class文件不符合要求的格式,Java虚拟机实现就抛出java.lang.ClassFormatError
异常。用classpy打开ClassFileTest.class文件,
可以看到,开头4字节确实是0xCAFEBABE
版本号
魔数之后是class文件的次版本号和主版本号,都是u2类型。
假设某class文件的主版本号是M,次版本号是m,那么完整的版本号可以表示成“M.m”的形式。
次版本号只在J2SE 1.2之前用过,从1.2开始基本上就没什么用了(都是0)。
主版本号在J2SE 1.2之前是45,从1.2开始,每次有大的Java版本发布,都会加1。
特定的Java虚拟机实现只能支持版本号在某个范围内的class文件。Oracle的实现是完全向后兼容的,比如Java SE 8支持版本号为45.0~52.0的class文件。
如果版本号不在支持的范围内,Java虚拟机实现就抛出java.lang.UnsupportedClassVersionError
异常。
Java 8就是52,支持版本号为45.0~52.0的class文件。
常量池
类访问标志
常量池之后是类访问标志,
这是一个16位的“bitmask”,指出:
class文件定义的是类还是接口,访问级别是public还是private,等等。
类和超类索引
类访问标志之后是两个u2类型的常量池索引,分别给出类名和超类名。
class文件存储的类名 类似完全限定名,但是把点换成了斜线,Java语言规范把这种名字叫作二进制名(binary names)。
thisClass必须是有效的常量池索引。
superClass只在Object.class中是0,在其他class文件中必须是有效的常量池索引。
接口索引表
类和超类索引后面是接口索引表,表中存放的也是常量池索引,给出该类实现的所有接口的名字。
ClassFileTest没有实现接口,所以接口表是空的
字段和方法表
接口索引表之后是,
字段和方法的基本结构大致相同,差别仅在于属性表。
field_info {
u2 access_flags;//访问标志
u2 name_index;//常量池索引 名字
u2 descriptor_index;//常量池索引 描述符
//属性表
u2 attributes_count;
attribute_info attributes[attributes_count];
}
属性
方法表后面 是类的属性,除了Class有属性,field和method都有属性,method的Code属性还有属性
属性,按名字区别,结构是这样
attribute_info {
u2 attribute_name_index;//属性名,常量池引用
u4 attribute_length;//属性长度
//其他
}