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

7
这个问题可能有点傻,或者可能是一个重复的问题。我对程序引用变量时如何从堆栈中检索变量感到困惑。 一个对象存储在堆中,其位置存储在引用变量中,并且包含堆地址的引用变量本身存储在堆栈中。但是JVM如何找出该引用变量存储在堆栈的哪个位置。 为了澄清我的疑惑,让我们考虑以下示例。
Class Test {
    public void test() {
        Object a = new Bar();
        Object b = new Foo();
        System.out.println(a);
    }
}

假设方法test()正在执行,那么将为test()分配堆栈。
现在当执行行“Object a = new Bar();”时,Bar对象将在堆中创建,并将实际变量'a'(其值是Bar对象的地址位置)存储在test()的堆栈中。
同样,在行“Object b = new Foo();”上发生相同的事情。Foo对象将在堆中创建,并将实际变量'b'(其值是Foo对象的地址位置)存储在test()的堆栈中。
现在当执行行“System.out.println(a);”时,JVM如何知道从堆栈中哪个位置检索变量'a'的值?这意味着什么将变量'a'与其在堆栈中的位置链接起来?
3个回答

11
你已经接近成功,只有一个理解的遗漏。
局部变量(或者说非基本类型存储在局部变量表中的对象引用)实际上是存储在“局部变量表”中的,而不是操作数栈中。只有在调用时,它们才会被推送到栈上供使用。
(让人困惑的是,局部变量表本身也存储在堆栈中,但这是与字节码用于操作数的堆栈分开的堆栈。从字节码的角度来看,它是一个真正的表格,具有固定大小和自由索引。)
你可以使用“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个本地变量!非静态方法总是有第“0”个参数:“this”。这解释了参数计数,并引导我们发现下一个重要问题:方法的参数也存储在本地变量表中。因此,我们的表将有0、1、2条目,其中0包含this,1和2未初始化。搞定后,让我们看看代码!首先是代码行0-7
  1. new 操作码创建一个 Bar 类的新实例,并将引用存储在堆栈上。
  2. dup 在堆栈顶部创建相同引用的副本(现在有两个副本坐在那里)
  3. invokespecial #3 调用 Bar 构造函数并消耗堆栈顶部。(现在我们只剩下一个副本了)
  4. astore_1 将剩余的引用存储在局部变量号码 1 中(在这种情况下,0 是为了 this

这就是 Object a = new Bar(); 编译后的结果。然后你可以得到相同的结果 Object b = new Foo();(代码行 8-15)。

然后来到有趣的部分,从第 16 行开始:

  1. getstatic #6System.out 的值推送到栈上。
  2. aload_1 也将局部变量编号为 1 (a) 推送到栈上。
  3. invokevirtual #7 消耗这两个条目,使用 a 作为其输入参数,在 System.out 上调用 println()

如果您想更深入地了解它,或者只是想指出我的错误,请参阅所有上述内容的官方参考资料here


感谢您详细的回复。只是澄清一下:astore_x会将数据推送到本地变量表中的位置'x',而aload_x会将存储在表中位置'x'的项目推送到堆栈中。这是通用的想法,对吗? - Dinkan
@Dinkan 没错,当然 astore_x 也会从堆栈中弹出该值。 - biziclop
好的。明白了。谢谢。 - Dinkan

0

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

以下是更详细的解释:JVM体系结构


0
JVM 存储堆栈帧,这些帧包含变量的数组。
Each frame (§2.6) contains an array of variables known as its local variables.
[...]
Local variables are addressed by indexing.

在这里找到了这里


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接