Java类文件结构
代码编译的结果从本地机器码转变为字节码,是存储格式的一小步,确实编程语言发展的一大步。
无关性的基石
各种不同平台的虚拟机与所有平台都统一使用的程序存储格式——字节码(ByteCode),实现语言无关性的基础仍然是虚拟机和字节码存储格式。Java虚拟机不和包括Java在内的任何语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联。
Java语言中的各种变量,关键字和运算符号的语义最终都是由多条字节码命令组合而成的,因此字节码命令所能提供的语言描述能力肯定会比Java语言本身更加强大。
Class类文件的结构
任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,类或接口并不一定都得定义在文件里(比如类或接口也可以通过类加载器直接生成)。
Class文件是一组以8位字节为基础单位的二进制流,各项数据项目严格按照顺序紧凑的排列在Class文件中,中间没有添加任何分隔符。Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表。
- 无符号数属于基本的数据类型,以u1、u2、u4和u8来分别表示1个字节、2个字节、4个字节和8个字节的无符号数,无符号数用来描述:数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
- 表是由多个无符号数或者其他表作为数据项构成的复合数据类型(表的类型名习惯以_info结尾)。可以这样理解,每种类型的表就是一种数据格式的约定,其规定了表中允许出现哪些数据项、以及它们的数据类型(无符号数或表)以及它们在表中出现的顺序。
魔数(Magic Number)
每个Class文件的头4个字节称为魔术,它唯一的作用就是确定这个文件是否为一个能被虚拟机接受的Class文件。
版本号
紧接着魔数的4个字节存储的是Class文件的版本号:第5和6个字节是次版本号(Minor Version),第7和8个字节是主版本号(Major Version)
常量池
紧接着主次版本号之后的是常量池入口,常量池可以理解为Class文件之中的资源仓库,也是占用Class文件空间最大的数据项目之一。
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。
字面量比较接近于Java语言层面的常量概念,如文本字符串、申明为final的常量值等。
而符号引用则属于编译原理方面的概念,包括了下面三类常量:
- 类和借口的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
Java代码在进行Javac编译的时候,并不像c/c++那样有“连接”这个步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法直接得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获取对应的符号引用,然后在类创建时或运行时解析、翻译到具体的内存地址之中。
常量池中的每一项常量都是一个表,
之所以说常量池是最烦琐的数据,是因为这14中常量类型各自均有自己的结构。
访问标志(access_flags)
在常量池结束后,紧接着两个字节代表访问标志,这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等。
类索引、父类索引与接口索引集合
类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,这里也验证了java是单继承体系。因为类实现接口的数量是不确定的,因此接口索引集合有一个前置的容量计数器(interfaces_count),类型为u2。此外,类索引、父类索引和接口索引都是u2类型的索引值,它们各自指向一个常量池中的常量。
Class文件中由这三个数据项来确定这个类的继承关系。
字段表集合(Field_info)
字段表用于描述接口或者类中声明的变量,字段包括类级变量以及实例变量,但不包括在方法内部声明的局部变量,我们可以想一想,在Java中描述一个字段需要哪些方面的信息?(public、private和protected)、static、final、volatile、transient等修饰符,以及字段数据类型和字段名称。Java支持的修饰符是确定的,对于各个修饰符,只需要一个bit位标记即可。而字段数据类型和字段名称,这些都是无法固定的,只能引用常量池中的常量来描述。
方法表集合(methods)
方法表的结构如同字段表一样,依次包含了访问标志、名称索引、描述符索引(指向方法特征签名的描述符)、属性表集合。
有人不禁会问,那么方法里面的Java代码区那里了呢?
方法里边的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为Code的属性里。
与字段表集合相对应的,如果父类方法在子类中没有被重写,方法表集合中就不会出现来自父类的方法信息。但同样的,有可能会出现由编译器自动添加的方法,最典型的便是类的构造器“<clinit>"方法和实例构造器”<init>“方法。
在Java语言中,要重写(Override)一个方法,除了要与原来方法具有相同的简单名称之外,还需要具有相同的特征签名(包括参数列表和返回值)。
属性表集合(attributes)
在Class文件、字段表、方法表中都可以携带自己的属性表集合,用于描述某些场景专有的信息。常见属性如下:
Code属性:用于描述代码。
Exception属性:列出方法中可能抛出的受检查异常。
ConstantValue属性:该属性的作用是通知虚拟机自动为静态常量赋值。
InnerClasses属性:用于记录内部类与宿主类之间的关联关系。