执行引擎是java虚拟机最核心的组成部分之一。在java虚拟机中制定了虚拟机字节码执行引擎的概念模型,这个概念模型成了各类虚拟机执行引擎的统一外观。在不一样的虚拟机实现里面,执行引擎可能会有解释执行和编译执行两种选择,也可能二者兼备,甚至还可能会包含几个不一样级别的编译器执行引擎,可是从外观上看起来,全部的java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。java
运行时栈帧结构数组
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。每个栈帧都包括了局部变量表、操做数栈、动态链接、方法返回地址和一些额外的附加信息。编译代码的时候,须要多大的局部变量表,多深的操做数栈都已经彻底肯定了,而且写入了方法表的Code属性当中。对于执行引擎来讲,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的称为当前方法。执行引擎运行的全部字节码指令都只针对当前栈帧进行操做。
数据结构
概念模型:优化
1.局部变量表spa
局部变量表是一组变量存储空间,用于存放方法参数和方法内定义的局部变量。以变量槽位最小单位(虚拟机规范中并无明确指明一个变量槽到底多大,只是说每一个变量槽都能存放一个boolean byte char short int float reference returnAddress类型的数据),这些都是32的长度的内存空间,因此变量槽会随状况而自动调整,对于64位的数据类型,虚拟机会以高位对齐的方式为其分配两个连续的内存槽空间。64位的数据类型只有long和double两种,这里把long和double数据类型读写分割为两次32位读写的作法与“long和double的非原子性协定”中把一次long和double数据类型读写分割为两次32位读写的作法有些相似。线程
虚拟机经过索引定位的方式使用局部变量表,索引值的范围从0开始至局部变量表最大变量槽数量。若是是访问的是32位的数据,那么索引n就表明使用第n个变量槽,若是访问的是64位,那么就说明的是使用了n和n+1两个变量槽。对于两个相邻变量槽储存的64位数据,不容许使用任何方式单独访问其中的某一个。设计
方法执行时,虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程的,若是执行的是实例方法,那局部变量表地0位索引变量槽默认是用于传递方法所属对象的实例引用,其与参数则按照参数表顺序排列,占用从1开始的局部变量变量槽,参数表分配完毕后,再根据方法体内部定义的变量顺序和做用域分配其与的变量槽。code
为了尽量节省栈帧空间,局部变量表中的变量槽是能够重用的,方法体中定义的变量,其做用域不必定会覆盖整个方法体,若是当前字节码PC结束期的值已经超出了某个变量的做用域,那这个变量对应的变量槽就能够交给其余变量使用,不过这样的设计会带来一些额外的反作用。对象
例如:变量的复用可能会直接影响系统的垃圾收集行为blog
public static void main(String[] args){ byte[] placeholder=new byte[64*1024*1024]; System.gc(); }
这时候没有回收,由于placeholder对象还处于做用域以内。
public static void main(String[] args){ { byte[] placeholder=new byte[64*1024*1024]; } System.gc(); }
加了花括号,placeholder对象的做用域被限制在了花括号以内,从代码逻辑上讲,在执行System.gc()的时候,placeholder对象已经不可能再被访问了,可是发现仍是没有被回收,再改一下
public static void main(String[] args){ { byte[] placeholder=new byte[64*1024*1024]; } int a=0; System.gc(); }
在调用System.gc()方法以前加一行int a=0,虽然很莫名其妙,可是对象被回收了。
在这三段代码中,placeholder对象可否被回收的根本缘由是:局部变量表中的变量槽中是否还存有关于placeholder数组对象的引用。第一次修改中,虽然代码已经离开了placeholder的做用域,可是以后,没有任何对局部变量表的读写操做,placeholder本来所占用的变量槽尚未被其余变量复用,因此做为GC Roots一部分的局部变量表仍然保持着对它的关联,这种关联没被及时打断。能够选择将不使用的对象手动赋值为null,来代替int a=0;
还有一点是局部变量没有类变量那样的“准备阶段”所以必须赋初值。
2.操做数栈
操做数栈能够听任何类型的java数据,32位数据类型所占的栈容量为1,64位为2,在任什么时候候,操做数栈的深度都不会超过code表中设置的最大值。
操做数栈中元素的数据类型必须与字节码指令的序列严格匹配,在编译程序代码的时候,编译器要严格保证这一点,在类校验阶段数据流分析中还要再次验证这一点。
另外,在概念模型中,两个栈帧做为虚拟机栈的元素,是彻底互相独立的。不少虚拟机里会作一点优化,让下面栈帧的部分操做数栈和上面栈帧的部分局部变量表重叠在一块儿,这样在进行方法调用时就能够共用一部分数据
java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中的栈指的就是操做数栈。
3.动态链接
每一个栈帧都包含了一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程当中的动态链接。
常量池中有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用做为参数,这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这种转化成为静态解析;另一部分将在每一次运行期间转化为直接引用,这部分就称为动态链接
4.方法返回地址
一个方法运行之后,有两种方式能够退出这个方法:
①执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者,是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法称为正常完成出口
②方法执行过程当中遇到了异常,而且这个异常没有在方法体内获得处理,不管是虚拟机内部的异常,仍是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会致使方法退出,这种退出方式叫异常完成出口
不管哪一种退出方式,方法退出以后,都须要返回到方法被调用的位置,程序才能继续执行,方法返回时可能须要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。通常来讲,方法正常退出时,程序计数器的值能够做为返回地址,栈帧中极可能会保存这个计数器值。而方法异常退出时,返回地址是要经过异常处理器表来肯定的,栈帧中通常不会保存这部分信息。
5.附加信息
虚拟机规范容许具体的虚拟机实现增长一些规范里没有描述的信息到栈帧之中。
通常会把动态链接、方法返回地址与其余附加信息称为栈帧信息