part3 虚拟机执行子系统
本部分讲虚拟机的执行过程所涉及到的一些问题。这部分详细地说明了Java是如何实现平台无关的:JVM和字节码存储格式。通过设计一个统一的Class文件标准去存储字节码(JVM指令集,符号表及其他辅助信息),并制定规范进行语法和结构化约束,使用JVM的执行引擎去进行解释执行,最终实现平台无关。
虚拟机执行整个流程:首先,由编译器将java文件编译成Class文件,然后通过一整个类加载过程,将Class文件加载到内存的方法区,最终由执行引擎对字节码指令进行解释执行。
ch6 类文件结构
本章的内容细节很多,这里不会花很多笔墨去重复细节——这些细节自己看书会效果好一些;取而代之,会记录一些我自己认为的重点和要点。
一 平台无关
本部分的核心就是讲述Java是如何实现平台无关性的。早期语言如C以及很多高级类C语言都是平台相关的,其本质原因是其源代码最终会被编译成机器码由终端CPU执行。因为终端指令集的多样性,所以通过机器码是无法实现平台无关的。类似Java的语言就另辟蹊径:去实现自己的文件结构系统和对应的指令集,在真实机器和源代码之间加上一层虚拟机,用以执行自己特定的文件结构。这就是Java平台无关性的基石:字节码和JVM。其中,字节码存储信息,JVM去执行对应字节码。
这里需要说明的是,早在Java发展之初,Java和JVM就是分开设计的。JVM的适用范围远不止Java:只要能被对应编译器编译成字节码的语言,JVM都能进行解释执行,包括JRuby,Jython,Groovy等。当年的设计者野心就很大,厉害。
二 Class文件的结构
首先,说明一下,这部分内容为本章重点,但比较琐碎。书中讲的很好,图、表、文都有,有兴趣建议直接看书,更详细的可以看作者翻译的Java虚拟机规范。
一个Class文件对应唯一一个类或接口的定义信息,其由一组以8位字节为基本单位的二进制流组成,各数据项目严格按顺序排列在文件中,无间隔。当遇到超过8个字节的数据项时,按高位在前进行切割。
Class文件以一种类似于C中结构体的伪结构实现数据存储,该伪结构只含无符号数和表。这里Class文件本身就是一个表,表属于递归定义。无符号数描述数字、索引引用、数量值和字符串值。表由无符号数和表构成。
由于没有间隔,Class文件中的数据项细节被严格限定,每个类型的集合前都有容量计数器,方便查找。关于这块的细节,详见这篇文章类文件结构,这里不在累述。需要提一句的是,如果想看字节码指令的话,可以在Eclipse中下载Bytecode Outline插件。
三 字节码指令
JVM的字节码指令由一个字节长度的、代表某种特定操作意义的操作码和其后的零个至多个操作数构成。字节码指令集架构特色:
- 操作码长度为一个字节——其总数不超过256
- 处理超过一个字节数字时,需要进行数据重构
- 追求小数据量、高传输效率
不考虑异常处理的JVM解释器执行模型:
do {
自动计算PC寄存器的值+1;
根据PC寄存器的指示位置,从字节码流中娶错操作码;
if(字节码存在操作数)
从字节码流中去除操作数;
执行操作码所定义的操作;
} while (字节码流长度 > 0)
在JVM指令集中,大多数指令都包含了其操作对应的类型信息。由于操作码数量受限,所以并非所有类型提供了所有操作类型,这里JVM通过类型转换来处理。这也是byte型和short型会自动转型为int型进行操作的原因——很多指令没有其类型对应的操作码。
这里,作者把操作码主要分了九类:
1. 加载和存储指令:对于普通数值,存在加载和存储指令;对于常量,只有加载指令。
2.运算指令:对于普通运算,只支持int/long/float/double;对于位运算,只支持int/long;自增只支持int。
3.类型转换指令:宽化转换无需指令,窄化转化需要转化指令,在源代码层次是需要显示转换。需要说明的是:窄化转换导致的上溢下溢精度丢失等问题均不报错。
4.对象创建和访问指令:用于类实例和数组
5.操作数栈管理指令:pop、dup和swap,用以直接操作操作数栈。
6.控制转移指令:修改PC寄存器的值,实现跳转。
7.方法调用和返回指令:invokevirtual、invokeinterface、invokespecial、invokestatic和invokedynamic。
8.异常处理指令:抛出异常用athrow指令,处理异常不用指令而是用异常表实现。
9.同步指令:JVM通过monitorenter和monitorexit实现synchronized语义;其实现需要javac和JVM的协作支持。
四 Class文件结构发展
其文件主体结构、字节码指令基本没有变动;对其改进集中在向访问标识和属性表这种设计之初就可扩展的数据结构进行内容添加——说明其最初设计不仅野心很大,考虑的也非常周到。