从0到1理解JVM_字节码执行引擎

之前的博文里,详细地介绍了类的加载和对象的创建,我们现在的 JVM 已经有对象可以操作了,那么,对象的方法是如何执行的呢?

方法的调用

在 JVM 中,每个方法的调用都对应着一个栈中的栈帧,栈帧里包括了方法的局部变量表、操作数、动态连接、返回地址。

局部变量表,大小固定,保存着方法的参数和方法内部定义的局部变量。局部变量表的第一个元素默认是该方法所属对象的引用(这就是为什么我们在方法里用 this 可以引用到当前对象)。

操作数,一个后入先出的栈结构,当 JVM 执行指令时会将指令的操作数入栈,指令执行完就全部出栈。

动态连接,之前在类加载时我们知道,类加载时会有解析阶段,将符号引用解析为直接引用(也就是找到具体的内存地址),实际上,部分符号引用直到运行时才会被解析为直接引用,这就是动态连接。

返回地址,这很容易理解,一个方法执行完毕后,总得回到调用方法的地方继续执行。

一个方法的调用的过程,其实就是对应着生成栈帧,并将栈帧入栈的过程。

重载和重写

我们知道方法调用的过程是怎样的,那么有一个问题是,该调用哪个方法呢?

JAVA 具有多态的特性,也就是重载和重写。

重载,方法名相同而参数类型不同,在编译时就确定好改调用哪个方法,并将其存储在 class 文件中,解析阶段就可以将相应的符号引用转换为直接引用。

重写,子类覆盖掉父类的方法(异常必须更小,子类方法的访问权限必须更大,父类权限不能为 private),在运行时才将符号引用转换为直接引用。

方法执行

直到现在,我们知道调用哪个方法,知道如何调用(栈帧入栈),那么如何执行呢?

JVM 使用一种基于栈的指令集,而不是真实机器使用的基于寄存器的指令集,为了使用基于栈的指令集,先前我们准备的栈帧里的操作数栈是最关键的一点:执行指令时,不断地将操作数入栈出栈,进而做运算,直到最后,操作数栈的栈顶就是我们的执行结果