执行引擎是相对于“物理机”的概念,虚拟机的执行引擎是自己实现的,能够执行那些不被硬件直接支持的指令集格式。
执行引擎在执行Java代码的时候可能会有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码)两种选择。
运行时栈帧结构
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。
局部变量表
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。局部变量表的容量以变量槽(slot)为最小单位。一个slot可以存放一个32位以内的数据类型,Java中占用32位以内的数据类型有boolean、byte、char、short、int、float、reference和returnAddress 8中类型,reference类型表示对一个对象实例的引用。对于64位的数据类型,虚拟机会以高位对齐的方式为其分配两个连续的slot空间。
在方法执行时,虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程的,如果执行的是实例方法(非static方法),那局部变量表中第0位索引的slot默认是用于传递方法所属对象实例的引用,在方法中可以通过关键字this来访问到这个隐含的参数。其余参数按照参数表顺序排列,占用从1开始的局部变量slot,参数表分配完毕后,在根据方法体内部定义的变量顺序和作用域分配其余的slot。
操作数栈
操作数栈是一个后入先出栈,操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。
当一个方法刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作。
动态连接
符合引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这种转换称为静态解析,另外一部分将在每一次运行期间转化为直接引用,称为动态连接。
方法返回地址
当一个方法开始执行后,只有两种方式可以退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令称为正常完成出口。另一种退出方式是在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只有在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,称为异常完成出口。
无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。
方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等。
方法调用
方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本,暂时不涉及方法内部的具体运行过程。一切方法调用在Class文件里面存储的都只是符合引用,而不是方法在实际运行时内存布局中的入口地址。
解析
所有方法调用中的目标方法在Class文件里面都是一个常量池中的符合引用,在类加载的解析阶段,会将其中的一部分符合引用转化为直接引用,这种解析能成立的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的,符合这个条件的有静态方法、私有方法、实例构造器、父类方法4类。
分派
Human man = new Man();
前面的Human称为变量的静态类型或外观类型,后面的Man称为实际类型。
虚拟机(准确的说是编译器)在重载时是通过参数的静态类型而不是实际类型作为判断依据的。
所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。静态分派的典型应用是方法重载,静态分派发生在编译阶段。
在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。
invokevirtual指令的运行时解析过程大致分为以下几个步骤:
- 找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C;
- 如果在类型C中找到与常量中的描述符和简单名称都相符得方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回java.lang.IllegalAccessError异常。
- 否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。
- 如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。
动态类型语言支持