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

Java JVM如何知道变量在方法堆栈中的位置?,java,stack-memory,Java,Stack Memory,这个问题可能是愚蠢的,也可能是重复的。当程序引用变量时,我对如何从堆栈中检索变量感到困惑。 对象存储在堆中,位置存储在引用变量中,包含堆地址本身的引用变量存储在堆栈中。但JVM如何确定引用变量存储在堆栈中的哪个位置 让我们考虑一下这个例子,弄清楚我所困惑的是什么。 Class Test { public void test() { Object a = new Bar(); Object b = new Foo(); System.out.

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

让我们考虑一下这个例子,弄清楚我所困惑的是什么。

Class Test {
    public void test() {
        Object a = new Bar();
        Object b = new Foo();
        System.out.println(a);
    }
}
假设正在执行方法test()。因此,将为test()分配堆栈

现在,当行'对象a=new Bar()'执行后,Bar对象将在堆中创建,实际变量'a'的值是Bar对象的地址位置,将存储在test()的堆栈中

再次在“Object 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
}
元数据告诉JVM该方法的操作数堆栈深度不超过3个条目、3个局部变量和1个参数。但这肯定是不对的,我们的方法没有参数,显然只有2个局部变量

答案是非静态方法总是有一个“0参数”:
this
。这解释了参数计数,并将我们引向下一个重要发现:方法的参数也存储在局部变量表中。因此,我们的表将有条目0,1,2,其中0在开始处包含
this
,1和2未初始化

有了这些,让我们看看代码!首先是行
0-7

  • new
    操作码创建
    Bar
    的新实例,并将引用存储在堆栈上
  • dup
    在堆栈顶部创建同一引用的副本(因此您现在有两个副本)
  • invokespecial#3
    调用
    Bar
    的构造函数并占用堆栈顶部。(现在我们只剩下一份了)
  • astore_1
    将剩余的引用存储在局部变量编号
    1
    中(
    0
    在这种情况下用于
  • 这就是
    objecta=newbar()已编译为。然后对
    Object b=new Foo()得到相同的结果(行
    8-15

    然后是有趣的一点,从第16行开始:

  • getstatic#6
    System.out的值推送到堆栈上
  • aload_1
    也将本地变量1(
    a
    )推送到堆栈上
  • 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组织它需要的所有内存,并将它们分配到称为运行时数据区的几个不同内存堆栈中


    这里有一个更详细的解释:

    谢谢您的详细回复。只是澄清一下:astore_x将推送到局部变量表中的位置“x”,而aload_x将把存储在表中位置“x”的项推送到堆栈中。这就是rt?@Dinkan的一般想法,当然,
    astore_x
    也会从堆栈中弹出值。好的。知道了。谢谢
    Each frame (§2.6) contains an array of variables known as its local variables.
    [...]
    Local variables are addressed by indexing.