已知 JVM 线程运行时候,存在线程之间共享的堆内存,以及线程独有的线程栈。
其中堆区主要用来存储一些运行时数据,如启动时加载的类,创建的各种对象。而栈区则主要用来存储线程栈,每个线程栈中又是由函数栈帧组成,每个函数调用对应一个栈帧,其中包括:局部变量表,操作数栈等。
loading...
从例子入手#
java
public class Tmp {
public int a = 42;
public static void main(String[] args) {
Tmp tmp = new Tmp();
int x = tmp.a;
tmp.a = x + 1;
}
}
如果不考虑 main 函数,这个类在用javac
编译后大概是这样
java
public class Tmp {
public <init>()V
L0 LINENUMBER 1 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1 LINENUMBER 2 L1
ALOAD 0
BIPUSH 42
PUTFIELD Tmp.a : I
RETURN MAXSTACK = 2
MAXLOCALS = 1
}
而我们要执行的main
方法则是这样的
java
public static main([Ljava/lang/String;)V
// line 0 Tmp tmp = new Tmp();
NEW Tmp
DUP
INVOKESPECIAL Tmp.<init> ()V
ASTORE 1
// line 1 int x = tmp.a;
ALOAD 1
GETFIELD Tmp.a : I
ISTORE 2
// line 2 tmp.a = x + 1;
ALOAD 1
ILOAD 2
ICONST_1
IADD
PUTFIELD Tmp.a : I
// line 3 return line
RETURN MAXSTACK = 3
MAXLOCALS = 3
loading...
当执行第 0 行Tmp tmp = new Tmp();
的时候,会进行如下操作:
NEW Tmp
,在堆中申请一块 Tmp 对象的内存,并将其引用压入操作数栈。DUP
复制操作数栈栈顶的元素(也就是 Tmp 的引用)INVOKESPECIAL Tmp.<init> ()V
执行 Tmp 的初始化函数。ASTORE 1
将操作数栈顶的元素出栈放入操作数栈
loading...
紧接着第 1 行int x = tmp.a;
ALOAD 1
将局部变量表中的#1
放入操作数栈。GETFIELD Tmp.a : I
弹出操作数栈顶的引用,将其作为 Tmp 从堆中取字段a
的值(Int 类型)放入栈顶ISTORE 2
弹出操作数栈顶,将其存入局部变量表#2
loading...
随后第 2 行tmp.a = x + 1;
ALOAD 1
将局部变量表中的#1
放入操作数栈。ILOAD 2
将局部变量表中的#2
放入操作数栈。ICONST_1
将 Int 常量放入操作数栈。IADD
弹出栈顶两个元素,将其相加,结果放入操作数栈。PUTFIELD Tmp.a : I
弹出栈顶元素 (上一条指令结果),弹出栈顶(tmp 引用),将其放入内存中tmp.a
字段中。
loading...
最后则是main
方法的返回语句,直接跳过。
从上面的例子不难看出,JVM 是基于 Stack 的 Virtual Machine。与在 Architecture 中介绍的寄存器式 CPU 有很大不同。