概述
执行引擎是java虚拟机核心的组成部分之一。“虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的,而虚拟机的执行引擎是由软件自行实现的,因此可以不受物理条件约束地定制指令集与执行引擎的结构体系,能够执行那些不被硬件直接支持的指令集格式。
在《java虚拟机规范》中制定了java虚拟机字节码执行引擎的概念模型,不同的虚拟机实现中,执行引擎在执行字节码的时候,通常会有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种选择,也可能两者兼备。
运行时栈帧结构
java虚拟机以方法最为最基本的执行单元,“栈帧”(stack frame)则是用于支持虚拟机进行方法调用和方法执行背后的数据结构,它也是虚拟机运行时数据区中的虚拟机栈(virtual machine stack)的栈元素。每一个方法从调用开始至执行结束的过程,都对应着一个栈帧在虚拟机里面从入栈到出栈的过程。
一个线程中的方法调用链可能会很长,以java程序的角度来看,同一时刻、同一条线程里面,在调用栈帧的所有方法都同时处于执行状态。而对于执行引擎来讲,在活动线程中,只有位于栈顶的方法才是运行的,只有位于栈顶的方法才是生效的,其被称为“当前栈顶”(current stack frame),与这个栈帧所关联的方法被称为“当前方法”(current method),执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。
每一个栈帧都包含了局部变量表、操作数栈、动态连接、方法返回地址和一些额外的附加信息。在编译java程序源码的时候,栈帧中需要多大的局部变量表,需要多深的操作数栈就已经被分析计算出来,并且写入到方法表的Code属性之中。换言之,一个栈帧所需要分配多少内存,并不会收到程序运行期变量数据的影响,而仅仅取决于程序源码和具体的虚拟机实现的栈内存布局形式。
局部变量表
局部变量表(local variables table)是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。
局部变量表的容量以变量槽(variable slot)为最小单位,一个变量槽可以存放一个32位以内的数据类型,java中占用不超过32位存储空间的数据类型有boolean、byte、short、int、float、reference和returnAddress这8种类型。reference类型表示对一个对象的引用,《java虚拟机规范》既没有说明它的长度,也没有明确指出这种引用应有怎样的结构,但是一般来说,虚拟机实现至少都应当能通过这个引用做到两件事,一是从根据引用直接或间接地查到对象在java堆中的数据存放的起始地址或索引;二是根据引用直接或间接的查找到对象所属数据类型在方法区中的存储的类型信息。
java虚拟机通过索引定位的方式使用局部变量表,索引的范围是从0开始至局部变量表最大的变量槽数量。
当一个方法被调用时,java虚拟机会使用局部变量表来完成参数值到参数变量列表的传递过程,即实参到形参的传递。如果执行的是实例方法(没有被static修饰的方法),那局部变量表中第0位索引的变量槽默认是用于传递方法所属对象实例的引用,在方法中可以通过关键字“this”来访问到这个隐含参数。
操作数栈
操作数栈(operand stack)也被称为操作栈,它是一个后入先出(last in first out,LIFO)栈。当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作。
操作数栈中的元素的数据类型必须与字节码指令的序列严格匹配,在编译程序代码的时候,编译器必须严格保证这一点,在类校验阶段的数据流分析中还要再次验证这一点。
动态连接
每个栈帧都包含一个执行运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池里指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就被转化为直接引用,这种转化被称为静态解析。另外一部分将在每一次运行期间都转化为直接引用,这部分就成为动态连接。
方法返回地址
当一个方法开始执行后,只有两种方式退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的方法称为调用者或者主调方法),方法是否有返回值以及返回值的类型将根据遇到何种返回指令来决定,这种退出方法称为“正常调用完成(normal method invocation completion)”;另外一种退出方式是在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到妥善处理。无论是java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法称为“异常调用完成(abrupt method invocation completion)”。
~持续更新~
注:摘自《深入理解Java虚拟机》周志明