3.1 无关性的基石
实现语言无关性的基石是虚拟机和字节码的存储格式。JVM不和包括Java在内的任何语言绑定,它只与Class文件这种特定的二进制文件格式所关联,Class文件中包含了JVM指令集和符号表以及其他若干辅助信息。基于安全方面的考虑,JVM规范要求在Class文件中使用许多强制性的语法和结构化约束,但任何一门功能性的语言都可以表示成一个能被Java虚拟机所接受的有效的Class文件。
3.2 Class文件结构
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件中,中间没有添加任何分隔符,这使Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。当遇到需要占用8位字节以上空间的数据项时,则会按照高位在前的方式分割成若干了8位字节进行存储。
根据JVM规范的规定,Class文件格式采用一种C语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型:无符号数和表,后面的解析都以它们为基础。
3.2.1 魔数与Class文件的版本
每个Class文件的头4个字节称为魔数,它的唯一作用是确定这个文件是否能被虚拟机识别的Class文件。紧接着魔数的4个字节存储的Class文件的版本号。
3.2.2 常量池
紧接着主次版本号之后的是常量池入口,常量池可以理解为Class文件中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时还是Class文件中第一个出现的表类型数据项目。
常量池中主要存放两大类常量:字面量和符号引用。字面量类似Java语言中的常量,如文本字符串,声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括:类和接口的全限定名,字段的名称和描述符,方法的名称和描述符。
3.2.3 访问标志
常量池结束之后,紧接着两个字节代表访问标志,这个标志用于识别一些类和接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型等。
3.2.4 类索引,父类索引和接口索引集合
类索引和父类索引都是一个u2类型的数据,而接口索引集合是一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系。类索引确定这个类的全限定名,父类索引确定这个类的父类的全限定名。由于Java不运行多重继承,所以父类索引就只有一个,除了java.lang.Object之外,所有的Java类都有父类,因此这些类的父类索引都不为0。接口索引计划就用来描述这个类实现了哪些接口,这些被实现的接口将按implements后的接口顺序从左到右排列在接口索引集合中。
3.2.5 字段表集合
字段表用于描述接口或者类中声明的变量。字段包括类级变量以及实例级变量,但不包括方法内部声明的局部变量。
3.2.6 方法表集合
与字段描述几乎完全一致。
3.2.7 属性表集合
在Class文件、字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息。
3.3 字节码指令
Java虚拟机的指令是由一个字节长度的、代表着某种特定含义的数字(操作码)以及跟随其后的0至多个代表此操作所需参数(操作数)构成。由于JVM采用面向操作数栈而不是寄存器的架构,所以大多数指令都不包含操作数,只有一个操作码。
3.3.1 字节码与数据类型
在Java虚拟机指令中,大多数指令都包含了其操作所对应的数据类型信息。例如,iload指令用于从局部变量表中加载int型的数据到操作数栈中,而fload则加载float类型的数据。这两条指令在虚拟机内部可能是由同一段代码来实现的,但在Class文件中它们必须拥有各自独立的操作码。
3.3.2 加载和存储指令
加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。
3.3.3 运算指令
运算或算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。大体上算术指令可分为两种:对整型数据运算的指令和对浮点型数据运算的指令,都使用虚拟机的数据类型,由于没有直接支持byte,short,char和boolean类型的算术指令,对于这类数据的运算,使用int类型的指令代替。
3.3.4 类型转换指令
类型转换指令可以将两种不同的数值类型进行相互转换,这些转换操作一半用于实现用户代码中的显式类型转换操作,或者用来处理字节码指令集中数据类型相关指令无法与数据类型一一对应的问题。
JVM直接支持宽化类型转换,窄化类型转换时,必须显式地使用转换指令。
3.3.5 对象创建与访问指令
JVM对类实例和数组的创建与操作使用了不同的字节码指令。对象创建后,就可以通过对象访问指令获取对象实例或者数组实例中的字段或数组元素。
3.3.6 操作数栈管理指令
JVM提供了一些用于直接操作操作数栈的指令。
3.3.7 控制转移指令
让JVM有条件或无条件地从指定的位置指令而不是控制转移指令的下一条指令继续执行程序,从概念模型上理解,可以认为控制转移指令就是在有条件或无条件地修改PC寄存器的值。
3.3.8 异常处理指令
在Java程序中显式抛出异常的操作(throw)都由athrow指令来实现,除了用throw语句显式抛出异常之外,JVM规范还规定了许多运行时异常会在其他JVM指令检测到异常状况时自动抛出。
在JVM中,处理异常(catch)不是由字节码指令来实现的,而采用异常表来完成的。
3.3.9 同步指令
JVM可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程来支持的。