1. 栈帧(Stack Frame)
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态链接和方法返回地址等信息。
程序在编译时,栈帧需要多大的局部变量表,多深得操作数栈都已经确定,并写到方法表的Code属性中。
1.1 局部变量表:存放方法参数和方法内部定义的局部变量。在java程序编译为class文件时,就在方法的code属性的max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量。
1.2 操作数栈,配合计算使用,例如加法乘法
1.3 动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用时为了支持方法调用过程中的动态链接。
1.4 方法返回。方法退出有两种情况,一种是正常退出,一种是异常退出。方法正常退出时,调用者的PC计数器的值可以作为返回地址,栈帧会保存这个计数器值。异常退出时,返回地址要通过异常处理器表来确定。方法退出实际上就等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令。
2. 方法调用
方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本。
2.1 解析:调用目标在程序代码写好、编译器进行编译时就必须确定下来,这类方法调用称为解析(Resolution)。符合解析的方法主要包括静态方法和私有方法。
2.2 分派:分派揭示了多态特征的一些最基本的体现,如重载和重写。
2.2.1 静态分派:虚拟机在重载时是通过参数的静态类型,而不是实际类型作为判定依据的。所有依赖静态类型来定位方法执行版本的分派动作都叫静态分派。在找不到对应的静态类型时,静态分派可以根据类型的优先级来选用方法执行版本,类型的优先级顺序是:char -> int -> long -> float -> double
2.2.2 动态分派:根据实际类型来分派方法执行版本,这种叫动态分派,重写即是使用这种方法。
2.2.3 单分派和多分派:方法的接收者和参数统称为方法的宗量,单分派是指使用一个宗量来决定,多分派是指使用多个分派来决定。静态分派属于多分派,动态分派属于单分派。
2.2.4 虚拟机动态分派的实现:动态分派非常频繁,为了提升性能,最常用的“稳定优化”方法就是为类在方法区中建立一个虚方法表(Virtual Method Table, vtable)。
vtable中存放着各个方法的实际入口地址。如果某个方法在子类中没有被重写,那子类的vtable里的地址入口和父类是一致的。如果子类重写了,那子类的方法地址入口就会换成子类的地址入口。方法表一般在类加载的连接阶段初始化,准备了类的变量初始值后,虚拟机会把该类的方法表也初始化完毕。
除了vtable,虚拟机还可以使用其它机制来优化,例如内联缓存和机遇“类型继承关系分析”技术和守护内联两种非稳定的“激进优化”。
3. 基于栈的字节码解释执行引擎
java编译器输出的指令流,是一种基于栈的指令集架构,与之对应的是基于寄存器的指令集。基于寄存器因为直接使用了硬件寄存器,所以性能高,但是可移植性差,因为不同类型芯片的寄存器数量是不一样的。基于栈的性能差些,但是可移植性强。