八、虚拟机字节码执行引擎
代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,却是编程语言发展的一大步。
一、运行时栈帧结构
一个线程中的调用链可能很长,很多方法同时处于执行状态。对执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。
1.局部变量表
1)存储内容
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
2)如何存储
局部变量表以变量槽(Variable Slot)为最小单位。那Slot到底多大呢?虚拟机规范并没有明确指明一个Slot所占用的空间大小,只是很有导向地说每个Slot都应能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据,这8种数据类型都可使用32位或更小的物理内存来存放。Slot长度可随着处理器、操作系统或虚拟机不同而发生变化。只要保证即时在64位虚拟机中使用了64位的物理内存空间去实现一个Slot,虚拟机仍要使用对齐和补白的手段让Slot在外观上看起来与32位虚拟机中的一致。
3)如何使用
虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从0开始至局部变量表最大的Slot数量。如果访问的是32位数据类型的变量,索引n就代表了使用第n个Slot,如果是64位数据类型的变量,则说明会同时使用n和n+1两个Slot。对于相邻的共同存放一个64位数据的两个Slot,不允许采用任何方式单独访问其中的某一个。
在方法执行时,虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程的。如果执行的是实例方法(即非static方法),那局部变量中第0位索引的Slot默认是用于传递方法所属对象实例的引用。在方法中可通过关键字“this”来访问到这个隐含的参数。接下来按声明顺序分配其余参数,占用从1开始的局部变量Slot,最后分配方法体中定义的其他变量。
2.操作数栈
1)存储内容
存储方法执行过程中用到的数据。
2)如何存储
32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2.
3)如何使用
当一个方法刚刚开始执行的时候,这个方法的操作数栈的空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,即出栈/入栈操作。在调用其他方法时也是通过操作数栈来进行参数传递的。
iadd在运行时操作数栈中最接近栈顶的两个元素已经存入两个int型的数值,当执行这个指令时,会将两个int值出栈并相加,然后将相加的结果入栈。
3.动态连接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。
4.方法返回地址
存储内容:
返回地址存储的内容由方法的不同退出方式决定。无论哪种退出方式,在方法退出后,都需要返回到方法调用的位置,程序才能继续执行。当方法正常退出时,调用者的程序计数器的值可作为返回地址,栈帧中很可能会保存这个计数器;而方法异常退出时,返回地址是通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。
二、方法调用
方法调用不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的执行过程。Class文件的编译过程不包含传统编译中的连接步骤,一切方法在Class文件中存储的都是符号引用,而不是方法在实际运行时内存布局的入口地址。如何确定Java调用的具体方法呢?
1.解析
所有方法调用中的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,虚拟机会将一部分符号引用转为直接引用。具体是哪部分呢?方法在运行前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。
对应的字节码指令是:
invokestatic:调用静态方法
invokespecial:调用实例构造器<init>方法、私有方法和父类方法
被这俩指令调用的方法,都可在解析阶段中确定唯一的调用版本,在类加载时就会把符号引用解析为直接引用。这些方法称为非虚方法,与之相反,其他方法称为虚方法(出去final方法)。final方法也是一种非虚方法。虚方法指令:
invokevirtual:调用所有虚方法
invokeinterface:调用接口方法,会在运行时确定一个实现此接口的对象
invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后执行该方法。前面4个指令的分派逻辑是固化在Java虚拟机内部的,而此指令的分派逻辑是由用户设定的引导方法决定的。
2.分派
1)静态分派
所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。静态分派的典型应用是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。
Human man = new Man();
“Human”称为变量的静态类型,“Man”称为变量的实际类型。
2)动态分派
典型应用是:方法重写。
三、方法执行
1.基于栈的执行过程
1.png其字节码指令为:
2.png
某些jdk编译的字节码指令会包含:
Code:
Stack=2, Locals=4, Args_size=1 //意思是这段代码需要深度为2的操作数栈和4个Slot的局部变量空间
用图来理解下执行方法的概念模型:
3.jpg 4.jpg 5.jpg