首页 文章

JVM如何知道方法堆栈中变量的位置?

提问于
浏览
5

这个问题可能是愚蠢的,也可能是重复的 . 当程序引用该变量时,我对如何从堆栈中检索变量感到困惑 . 对象存储在堆中,位置存储在引用变量中,包含堆地址本身的引用变量存储在堆栈中 . 但JVM如何确定该引用变量存储在堆栈中的哪个位置 .

让我们考虑这个例子只是为了弄清楚我对此感到困惑 .

Class Test {
    public void test() {
        Object a = new Bar();
        Object b = new Foo();
        System.out.println(a);
    }
}

让我们说方法test()正在执行 . 因此堆栈将被分配给test() .

现在当执行' Object a = new Bar(); '行时,将在Heap中创建Bar对象,并且实际变量 'a' (其值是Bar对象的地址位置)将存储在test()的堆栈中 .

再次在' Object b = new Foo(); '线上发生同样的事情 . Foo对象将在Heap中创建,实际变量 'b' (其值为Foo对象的地址位置)将存储在test()的堆栈中 .

现在,当执行' System.out.println(a); '行时,JVM如何知道堆栈中的哪个位置,应该检索 'a' 的值 . 意味着什么链接变量'a'及其在堆栈中的位置?

3 回答

  • 0

    你几乎就在那里,你的理解中只有一个缺失的环节 .

    局部变量(或存储在局部变量中的对象的引用,如果我们讨论的是非基本类型)实际上存储在 local variable table 中,而不是存储在操作数堆栈中 . 当它们被呼叫使用时,它们只被推入堆栈 .

    (令人困惑的是,局部变量表本身也存储在堆栈中,但是它是一个真实的表,具有固定的大小并且可以自由索引 . )

    您可以使用 javap 查看代码生成的字节码 . 你会看到的是这样的:

    public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=1
        0: new           #2                  // class Test$Bar
        3: dup
        4: invokespecial #3                  // Method Test$Bar."<init>":()V
        7: astore_1
        8: new           #4                  // class Test$Foo
       11: dup
       12: invokespecial #5                  // Method Test$Foo."<init>":()V
       15: astore_2
       16: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       19: aload_1
       20: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
       23: return
    }
    

    首先,这条线是什么?

    stack=3, locals=3, args_size=1
    

    元数据告诉JVM该方法的操作数堆栈不超过3个条目,3个局部变量并且取1个参数 . 但肯定是不对的,我们的方法不需要参数,显然只有2个局部变量!

    答案是非静态方法总是有"0th argument": this . 这解释了参数计数,并引导我们进入下一个重要发现: arguments of a method are stored in the local variable table too . 因此我们的表将有条目0,1,2,其中0在开始时包含 this ,而未初始化为1和2 .

    有了这个,让's look at the code! First up it'的行 0-7

    • new 操作码创建 Bar 的新实例并将引用存储在堆栈中 .

    • dup 在堆栈顶部创建相同引用的副本(所以你现在有两个副本)

    • invokespecial #3 调用 Bar 的构造函数并使用堆栈的顶部 . (现在我们只剩下一份)

    • astore_1 将剩余引用存储在本地变量号 1 中( 0 在本例中为 this

    这就是 Object a = new Bar(); 编译成的内容 . 然后你得到相同的 Object b = new Foo(); (行 8-15 ) .

    然后是有趣的一点,来自 16 行:

    • getstatic #6 在堆栈上推送 System.out 的值

    • aload_1 也会在堆栈上推送局部变量编号1( a

    • invokevirtual #7 消耗这两个条目,在 System.out 上调用 println() ,其中 a 作为其输入参数 .

    If you want to delve into it deeper, or you just want to point out my mistakes, the official reference for all of the above is here.

  • 0

    JVM存储堆栈帧,这些存储变量的数组 .

    Each frame (§2.6) contains an array of variables known as its local variables.
    [...]
    Local variables are addressed by indexing.
    

    找到Here

  • 7

    JVM不是一个单一的数据结构,实际上它有几种不同的机制 . 执行程序时,JVM会组织所需的所有内存,并将它们分配到几个称为运行时数据区的不同内存堆栈中 .

    这是一个更详细的解释:Architecture of JVM

相关问题