一、运行时栈结构
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构。
栈帧包括:局部变量表、操作数栈、动态连接、方法的返回地址 和一些额外的附加信息。
执行引擎运行的所有字节码指令只针对当前栈帧进行操作。需要注意的是一个栈中能容纳的栈帧是受限,过深的方法调用可能会导致StackOverFlowError,当然,我们可以认为设置栈的大小。其模型示意图大体如下:
执行引擎的所有字节码指令都只针对当前栈帧进行操作。
1、局部变量表
是变量值的存储空间,由方法参数和方法内部定义的局部变量组成,其容量用Slot1作为最小单位。
slot可以存放32位以内的数据类型。slot的长度可以随处理器、操作系统或虚拟机不同而发生改变。
局部变量表的大小在编译时就可以确定,Code中的max_local。
局部变量表不存在系统初始化的过程,这意味着一旦定义了局部变量则必须人为的初始化,否则无法使用。
JVM通过索引定位方式使用局部变量表。
在方法执行时,如果是实例方法,那么局部变量表的第0位索引的slot默认是用于传递方法所属类实例的引用(也就是this)。
2、操作数栈
操作数栈的最大深度也在编译的时候写入到Code属性的max_stacks数据项中。
另外我们说Java虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈。
操作数栈的每一个元素可以是任意java类型。
作用:在做算术运算的时候通过操作数栈来进行又或者调用其他方法的时候通过操作数栈来进行参数传递。
两个栈帧(局部变量表和操作数栈)是完全相互独立的,jvm会做一些优化处理,令两个栈帧出现一部分重叠。
3、动态连接
每一个栈帧中都包含一个指向运行时常量池中该栈所属方法的符号引用,持有该引用是为了支持方法调用过程中的动态连接(符号引用转换成直接引用)。
4、方法返回地址
方法执行后有两种方式可以退出这个方法:
(1)执行引擎遇到任意一个方法返回的字节码指令,也就是所谓的正常完成出口。
(2)在方法执行的过程中遇到了异常,并且这个异常没有在方法内进行处理,也就是只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种方式成为异常完成出口。
无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。这个就是存放返回位置的地址。
二、方法调用
方法调用不等同于方法执行,方法调用只是确定调用方法版本(哪个方法)。
1、解析(在类加载阶段,将其中一部分符号引用转换成直接引用)。
前提:方法在程序运行之前就可以确定调用的版本,并且运行期不可变。符合这个条件的有:静态方法、私有方法、实例构造器、父类方法,这些方法都可以称非虚方法(还有final),也就是不能多态。
2、分派
宗量:方法的接受者与方法的参数称为方法的宗量。
静态分派:所有依赖静态类型3来定位方法执行版本的分派成为静态分派,发生在编译阶段,典型应用是方法重载。
动态分派:在运行期间根据实际类型4来确定方法执行版本的分派成为动态分派,发生在程序运行期间,典型的应用是方法的重写。
单分派:根据一个宗量5 对目标方法进行选择。动态派就是其中一种。
多分派:根据多于一个宗量对目标方法进行选择。静态分派时其中一种。
三、基于栈的字节码执行引擎
public class MainTest {
public static int add(){
int result=0;
int i=2;
int j=3;
int c=5;
return result =(i+j)*c;
}
public static void main(String[] args) {
MainTest.add();
}
}
来源:CSDN
作者:Zesystem
链接:https://blog.csdn.net/weixin_44588495/article/details/104137067