Java Runtime Data Area & 函数调用栈 JVM Stack & 栈帧 Stack Frame

更多 Java 虚拟机方面的文章,请参见文集《Java 虚拟机》


运行时数据区 Runtime Data Area

JVM 执行 Java 程序时需要装载各种数据,比如类型信息(Class)、类型实例(Instance)、常量数据(Constant)、本地变量等。
不同的数据存放在不同的内存区中,这些数据内存区称作运行时数据区(Runtime Data Area)

运行时数据区有这样几个重要区:

  • JVM Stack(简称 Stack 或者虚拟机栈、线程栈、栈等)
  • Frame(又称StackFrame/栈帧、方法栈等)
  • Heap(堆/GC堆,即垃圾收集的对象所在区)

单个线程内共享的区:

  • PC Register 寄存器
  • JVM Stack 虚拟机栈
  • Native Method Stack 本地方法栈

所有线程共享的区:

  • Heap 堆
  • Method Area 方法区,其中包含 Runtime Constant Pool 常量池
运行时数据区 Runtime Data Area

函数调用栈 JVM Stack

结构:
{JVM Stack [Frame][Frame][Frame]... }

Java 的函数调用栈就是 Java 虚拟机栈,它是线程私有的,与线程一同被创建,用于存储栈帧 Stack Frame,如下图所示。

线程栈(VM Statck/Stack)包含的栈帧(Frame)

ThrowablegetStackTrace() 可以返回当前线程的虚拟机栈信息,返回数组的第一个元素是栈顶元素,最后一个元素是栈底元素

示例代码如下:

public class StackTrace_Test {
    public void function1() {
        function2();
    }

    public void function2() {
        function3();
    }

    public void function3() {
        Throwable ex = new Throwable();

        StackTraceElement[] stackElements = ex.getStackTrace();

        System.out.println("Stack Length: " + stackElements.length);

        for (StackTraceElement stackTraceElement : stackElements) {
            System.out.println("Method Name: "
                    + stackTraceElement.getMethodName() + " Line Number: "
                    + stackTraceElement.getLineNumber());
        }
    }

    public static void main(String[] args) {
        StackTrace_Test t = new StackTrace_Test();
        t.function1();
    }
}

输出如下:

Stack Length: 4
Method Name: function3 Line Number: 11
Method Name: function2 Line Number: 7
Method Name: function1 Line Number: 3
Method Name: main Line Number: 26

栈帧 Stack Frame

结构:
{Frame [ReturnValue] [LocalVariables[ ][ ][ ][ ]...] [OperandStack [ ][ ][ ]...] [ConstPoolRef] }

每次方法调用均会创建一个对应的 Frame,方法执行完毕或者异常终止,Frame 被销毁。
一个方法 A 调用另一个方法 B 时,A 的 Frame 停止,新的 Frame 被创建赋予 B,执行完毕后,把计算结果传递给 A,A 继续执行。

局部变量表 LocalVariables

局部变量表的大小在编译期就被确定,这些值并放在class文件中。

局部变量区被组织成一个以字长为单位、从 0 开始计数的数组

字节码指令通过以 0 开始的索引来使用其中的数据。

  • 类型为 int、float、refence(引用)和returnAdress(方法出口)的值在数组内只占据一项
  • 类型为 byte、short、char 的值在存入数组时都先转换为 int 值,因此同样只占据一项
  • 类型为 long 和 double 的值在数组中却占据了连续两项。

局部变量区包含了对应的 方法参数 和 局部变量!!!

Java代码 int a=0; int b=1; int c=2; 对应的局部变量表如下:

LocalVariableTable:
Start Length Slot Name Signature
2 12 0 a I
4 10 1 b I
6 8 2 c I

其中:

  • Start 变量偏移量。
  • Length 作用域范围长度。[ Start, Start + Length )就是该变量的作用域。
  • Slot 一个Slot能存储 32bit 的数据类型、引用、返回地址,long / dobule需要两个Slot。

操作数栈 OperandStack

操作数栈 OperandStack 的大小在编译期就被确定,这些值并放在class文件中。

Frame 被创建时,操作栈是空的。操作栈的每个项可以存放 JVM 的各种类型数据,包括 long / double。
操作栈有个栈深,long / double贡献两个栈深。
操作栈调用其它有返回结果的方法时,会把结果 push 到栈上。

当虚拟机调用一个方法时,它从对应的类的类型信息得到 局部变量表 LocalVariables 和 操作数栈 OperandStack 的大小,并据此分配栈帧内存,并压入 JVM Stack 中。


引用:
JVM中的Stack和Frame

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容