How Stack And Heap Work

最后修改于

已知 JVM 线程运行时候,存在线程之间共享的堆内存,以及线程独有的线程栈。
其中堆区主要用来存储一些运行时数据,如启动时加载的类,创建的各种对象。而栈区则主要用来存储线程栈,每个线程栈中又是由函数栈帧组成,每个函数调用对应一个栈帧,其中包括:局部变量表,操作数栈等。
loading...

从例子入手#

如果不考虑 main 函数,这个类在用javac编译后大概是这样

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方法则是这样的

    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();的时候,会进行如下操作:

  1. NEW Tmp,在堆中申请一块 Tmp 对象的内存,并将其引用压入操作数栈。
  2. DUP 复制操作数栈栈顶的元素(也就是 Tmp 的引用)
  3. INVOKESPECIAL Tmp.<init> ()V 执行 Tmp 的初始化函数。
  4. ASTORE 1 将操作数栈顶的元素出栈放入操作数栈
    loading...
    紧接着第 1 行int x = tmp.a;
  5. ALOAD 1 将局部变量表中的 #1 放入操作数栈。
  6. GETFIELD Tmp.a : I 弹出操作数栈顶的引用,将其作为 Tmp 从堆中取字段 a 的值(Int 类型)放入栈顶
  7. ISTORE 2 弹出操作数栈顶,将其存入局部变量表 #2
    loading...
    随后第 2 行tmp.a = x + 1;
  8. ALOAD 1 将局部变量表中的 #1 放入操作数栈。
  9. ILOAD 2 将局部变量表中的 #2 放入操作数栈。
  10. ICONST_1 将 Int 常量放入操作数栈。
  11. IADD 弹出栈顶两个元素,将其相加,结果放入操作数栈。
  12. PUTFIELD Tmp.a : I 弹出栈顶元素 (上一条指令结果),弹出栈顶(tmp 引用),将其放入内存中 tmp.a 字段中。
    loading...
    最后则是main 方法的返回语句,直接跳过。

从上面的例子不难看出,JVM 是基于 Stack 的 Virtual Machine。与在 Architecture 中介绍的寄存器式 CPU 有很大不同。