这个问题可能是一个愚蠢的问题,也可能是一个重复的问题。我对程序引用该变量时如何从堆栈中检索变量感到困惑。对象存储在堆中,位置存储在引用变量中,包含堆地址本身的引用变量存储在堆栈中。但是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
行:
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
作为其输入参数。
如果你想更深入地研究它,或者你只是想指出我的错误,以上所有内容的官方参考都在这里。
JVM存储堆栈帧,这些帧保存变量的数组。
Each frame (§2.6) contains an array of variables known as its local variables.
[...]
Local variables are addressed by indexing.
在这里找到
JVM不是一个单一的数据结构,实际上它有几种不同的机制。当程序被执行时,JVM组织它需要的所有内存,并将它们分配到几个不同的内存堆栈中,称为运行时数据区域。
下面是一个更详细的解释:JVM的架构