更多 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 常量池
函数调用栈 JVM Stack
结构:
{JVM Stack [Frame][Frame][Frame]... }
Java 的函数调用栈就是 Java 虚拟机栈,它是线程私有的,与线程一同被创建,用于存储栈帧 Stack Frame,如下图所示。
Throwable
的 getStackTrace()
可以返回当前线程的虚拟机栈信息,返回数组的第一个元素是栈顶元素,最后一个元素是栈底元素。
示例代码如下:
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