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



这个问题可能是一个愚蠢的问题,也可能是一个重复的问题。我对程序引用该变量时如何从堆栈中检索变量感到困惑。对象存储在堆中,位置存储在引用变量中,包含堆地址本身的引用变量存储在堆栈中。但是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(( 堆栈中。

同样在">对象b = new Foo((;">一行上,同样的事情发生了。Foo对象将在Heap中创建,实际变量"b">,其值是Foo对象的地址位置,将存储在test((堆栈中。

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

你快到了,你的理解中只缺少一个环节。

局部变量

(或对存储在局部变量中的对象的引用,如果我们谈论的是非基元类型(实际上存储在局部变量表中,而不是存储在操作数堆栈中。它们仅在调用使用时才被推送到堆栈上。

(令人困惑的是,局部变量表本身也存储在堆栈上,但这与字节码用于操作数的堆栈是分开的。从字节码的角度来看,它是一个真正的表,具有固定大小和可自由索引。

您可以使用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使用这两个条目,在System.out上调用println()a作为其输入参数。

如果你想更深入地研究它,或者你只是想指出我的错误,以上所有内容的官方参考都在这里。

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

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

在这里找到

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

下面是一个更详细的解释:JVM的架构

最新更新