java虚拟机栈介绍

对于每一个线程,JVM 都会在线程被创建的时候,创建一个单独的栈。

也就是说虚拟机栈的生命周期和线程是一致,并且是线程私有的。除了 Native 方法以外,Java 方法都是通过 Java 虚拟机栈来实现调用和执行过程的(需要程序技术器、堆、元空间内数据的配合)。

所以 Java 虚拟机栈是虚拟机执行引擎的核心之一。而 Java 虚拟机栈中出栈入栈的元素就称为「栈帧」。

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。

栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用至执行完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程。

栈对应线程,栈帧对应方法

在活动线程中, 只有位于栈顶的帧才是有效的, 称为当前栈帧。正在执行的方法称为当前方法。

在执行引擎运行时, 所有指令都只能针对当前栈帧进行操作。而 StackOverflowError 表示请求的栈溢出, 导致内存耗尽, 通常出现在递归方法中。

虚拟机栈通过 pop 和 push 的方式,对每个方法对应的活动栈帧进行运算处理,方法正常执行结束,肯定会跳转到另一个栈帧上。

在执行的过程中,如果出现了异常,会进行异常回溯,返回地址通过异常处理表确定。

可以看出栈帧在整个 JVM 体系中的地位颇高。下面也具体介绍一下栈帧中的存储信息。

java 操作数栈 栈帧 java方法栈帧_java 操作数栈 栈帧

 

java虚拟机栈:局部变量表

局部变量表就是存放方法参数和方法内部定义的局部变量的区域。

局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

这里直接上代码,更好理解。

public int test(int a, int b) {
    Object obj = new Object();
    return a + b;
}

如果局部变量是 Java 的 8 种基本基本数据类型,则存在局部变量表中,如果是引用类型。如 new 出来的 String,局部变量表中存的是引用,而实例在堆中。

java 操作数栈 栈帧 java方法栈帧_Java_02

 

java虚拟机:操作栈

操作数栈(Operand Stack)看名字可以知道是一个栈结构。

Java 虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。当 JVM 为方法创建栈帧的时候,在栈帧中为方法创建一个操作数栈,保证方法内指令可以完成工作。

还是用实操理解一下。

/**
 * @author Richard_yyf
 */
public class OperandStackTest {

    public int sum(int a, int b) {
        return a + b;
    }
}

编译生成 .class 文件之后,再反汇编查看汇编指令

> javac OperandStackTest.java
> javap -v OperandStackTest.class > 1.txt
public int sum(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3 // 最大栈深度为2 局部变量个数为3
         0: iload_1 // 局部变量1 压栈
         1: iload_2 // 局部变量2 压栈
         2: iadd    // 栈顶两个元素相加,计算结果压栈
         3: ireturn
      LineNumberTable:
        line 10: 0

java虚拟机栈:动态链接

每个栈帧中包含一个在常量池中对当前方法的引用, 目的是支持方法调用过程的动态连接。

java虚拟机栈: 方法返回地址

方法执行时有两种退出情况:

    正常退出,即正常执行到任何方法的返回字节码指令,如 RETURN、IRETURN、ARETURN 等

    异常退出

无论何种退出情况,都将返回至方法当前被调用的位置。方法退出的过程相当于弹出当前栈帧,退出可能有三种方式:

       返回值压入上层调用栈帧

       异常信息抛给能够处理的栈帧

       PC 计数器指向方法调用后的下一条指令

本地方法栈

本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,

而本地方法栈则为虚拟机使用到的 Native 方法服务。

在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。

甚至有的虚拟机(譬如 Sun HotSpot 虚拟机)直接就把本地方法栈和虚拟机栈合二为一。

与虚拟机栈一样,本地方法栈区域也会抛出 StackOverflowError 和 OutOfMemoryError 异常。