什么是虚拟机栈

虚拟机栈和程序计数器一样都是属于线程私有的,虚拟机栈为当前线程所执行的方法创建一个栈帧,栈帧中存储着 局部变量表、操作数栈、动态链接、方法出口等信息,每个方法从调用的开始到结束就对应着一个栈帧在虚拟机栈中入栈和出栈的过程,体现的是一个java方法在线程中的内存模型。

局部变量表

局部变量表中存储着各种在编译期就可知的java基本数据类型 (byte、short、char、int、boolean、float、long、double)对象的引用、returnAddress,在局部变量表中这些数据通常以变量槽来表示,虽然没有规定大小,但是最大不能超过32位,所以除了long和double两个64位的数据占用两个变量槽其余均各占用一个变量槽,变量槽的需要分配的空间在编译器就已经确定下来了,运行期间也不会被改变。

操作数栈

从名字上就知道这是进行计算的地方,操作数栈常称为操作栈,后进先出,既然是栈肯定就有深度的限制,操作数栈的深度在编译器就存放到了方法的Code属性的max_stacks数据项中。操作数栈中的数据类型可以是java的任意数据类型,所占空间如果是32位数据就占用一个栈容量,如果是64位数据就占用两个栈容量。
当一个方法或者字节码指令被执行的时候会从局部变量表或者对象的实例中复制变量或者常量入栈到操作数栈中进行计算然后将结果出栈到局部变量表中或者返回给方法的调用者,这也就是出栈入栈的过程。

动态链接

在一个class文件中,一个方法要调用其他方法,需要将这些方法的符号引用转化为其在内存地址中的直接引用,而符号引用存在于方法区中的运行时常量池。
Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程中的动态连接(Dynamic Linking)
这些符号引用一部分会在类加载阶段或者第一次使用时就直接转化为直接引用,这类转化称为静态解析。另一部分将在每次运行期间转化为直接引用,这类转化称为动态连接。

方法返回信息

当一个方法开始执行时,可能有两种方式退出该方法:

  • 正常完成出口
  • 异常完成出口

正常完成出口是指方法正常完成并退出,没有抛出任何异常(包括Java虚拟机异常以及执行时通过throw语句显示抛出的异常)。如果当前方法正常完成,则根据当前方法返回的字节码指令,这时有可能会有返回值传递给方法调用者(调用它的方法),或者无返回值。具体是否有返回值以及返回值的数据类型将根据该方法返回的字节码指令确定。
异常完成出口是指方法执行过程中遇到异常,并且这个异常在方法体内部没有得到处理,导致方法退出。
无论是Java虚拟机抛出的异常还是代码中使用athrow指令产生的异常,只要在本方法的异常表中没有搜索到相应的异常处理器,就会导致方法退出。
无论方法采用何种方式退出,在方法退出后都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在当前栈帧中保存一些信息,用来帮他恢复它的上层方法执行状态。
方法退出过程实际上就等同于把当前栈帧出栈,因此退出可以执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压如调用者的操作数栈中,调整PC计数器的值以指向方法调用指令后的下一条指令。
一般来说,方法正常退出时,调用者的PC计数值可以作为返回地址,栈帧中可能保存此计数值。而方法异常退出时,返回地址是通过异常处理器表确定的,栈帧中一般不会保存此部分信息。