《深入理解Java虚拟机》读书笔记——类文件结构

概述

Java虚拟机不和包括Java在内的任何语言绑定,它只与”Class文件”这种特定的二进制文件格式所关联,Class文件中包含了Java虚拟机指令集和符号表以及若干其他辅助信息。虚拟机并不关心Class的来源是何种语言。

Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分割符,这使得整个Class文件中存储的内容几乎全部都是程序运行的必要数据,没有空隙存在。
根据Java虚拟机规范的规定,Class文件格式采用一种类似C语言结构体的伪结构来存储数据,这种伪结构只有两种数据结构:无符号数和表。

无符号数:基本数据类型,以u1,u2,u4,u8来分别表示1个字节,2个字节,4个字节和8个字节的无符号数,无符号数可以用来描述数字,索引饮用,数量值或者按照UTF-8编码构成字符串值。

表:是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以”_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表。

Class类文件结构详解

魔数

0~3:每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。CLass文件的魔数值为:0xCAFEBABE。

版本号

4~7:第5和第6个字节是次版本号,第7和第8个字节是主版本号。即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过其版本号的Class文件。

常量池数量以及常量池

8~9:常量池容量计数值。由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值。假如常量池容量是0x0016,即十进制的22,这就代表常量池中有21项常量。

访问标志

在常量池结束之后,紧接着两个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被申明为final等。

类索引,父类索引与接口索引集合

类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合是一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。

字段表集合

字段表用于描述接口或者类中声明的变量。字段包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。

方法表集合

方法表的结构如同字段表一样,依次包括了访问标志,名称索引,描述索引,属性表集合。方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为“Code”的属性里面。如果父类方法在子类中没有被重写,方法表集合中就不会出现来自父类的方法信息。但同样的,有可能会出现编译器自动添加的方法,最典型的便是类构造器”“方法和实例构造方法”“方法。

属性表集合

  1. Code属性;Java程序方法体中的代码经过Javac编译器处理后,最终变成字节码指令存储在Code属性内。Code属性出现在方法表的属性集合之中,但并非所有的方法表都必须存在这个属性,譬如接口或者抽象类中的方法就不存在Code属性。
  2. Exceptions属性:作用是列举方法中可能抛出的受查异常,也就是方法描述时在throws关键字后面列举的异常。
  3. LineNumberTable属性:用于描述Java源码行号与字节码行号之间的对应关系。可以在Javac中分别使用-g:none或-g:lines选项来取消或要求生成这项信息。如果不生成,对程序运行产生的最主要的影响就是当抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候,也无法按照源码行来设置断电。
  4. LocalVariableTable属性:用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系。如果没有生成这项属性,最大的影响就是当其他人引用这个方法时,所有的参数名称都会丢失,IDE将会使用诸如arg0,arg1之类的占位符代替原有的参数名。
  5. SourceFile属性:用于记录生成这个Class文件的源码文件名称。如果不生成这项属性,当抛出异常时,堆栈中将不会显示出错代码所属的文件名。
  6. ConstantValue属性:通知虚拟机自动为静态变量赋值。只有被static关键字修饰的变量(类变量)才可以使用这项属性。
  7. InnerClasses属性:InnerClasses属性用于记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那编译器将会为它以及它所包含的内部类生成InnerClasses属性。