当在堆中移动对象时,JVM可以轻松地更新局部变量,静态引用,类实例或对象数组实例的引用。但是如何更新推送到操作数堆栈的引用?
局部变量和操作数堆栈中的条目之间没有根本区别。两者都生活在同一个堆栈框架中。两者均未正式声明,并且都需要JVM执行推断以识别其实际用途。
以下代码
public static void example() {
{
int foo = 42;
}
{
Object bar = "text";
}
{
long x = 100L;
}
{
Object foo, bar = new Object();
}
}
将(通常)编译为
public static void example();
Code:
0: bipush 42
2: istore_0
3: ldc #1 // String text
5: astore_0
6: ldc2_w #2 // long 100l
9: lstore_0
10: new #4 // class java/lang/Object
13: dup
14: invokespecial #5 // Method java/lang/Object."<init>":()V
17: astore_1
18: return
注意堆栈帧中索引0
处的局部变量如何用不同类型的值重新分配。另外,最后存储到变量索引1
会使索引0
处的变量无效,否则它将包含long
值的一半悬挂。
没有关于局部变量类型的其他提示,调试信息是可选的,并且仅当代码包含分支时,堆栈映射表才存在。
确定局部变量是否包含引用的唯一方法是遵循程序流程并追溯指令的效果。这确实意味着要推断操作数堆栈上的值,因为如果没有它,我们甚至都不知道store
指令放到变量中的内容。
验证者可以做到,甚至是必须的,垃圾收集器或JVM的任何支持代码也可以做到。一个实现甚至可能只有一个分析代码,其中保留了第一个分析的类型信息,这将是验证。]
但是即使每次垃圾收集器都需要重建此信息时,开销也不会是天文数字。垃圾收集器仅定期运行,并且对于当前执行的方法仅需要此信息。这仅涉及解释执行。
当JIT编译器生成代码时,它仍然需要利用类型信息并可以为垃圾收集器准备信息,但是它只会对称为safepoints
的某些点进行处理,其中生成的代码会检查是否存在未完成的代码。垃圾收集。这意味着在这些点之间,数据不需要采用垃圾收集器能够理解的形式,并且优化的代码可以假定垃圾收集器在处理对象时不会重定位对象。这还意味着在编译的优化代码中,可达性可能与简单的解释执行中的完全不同,即,可能不存在未使用的变量,但是即使从源代码的角度来看,使用中的对象也可能在优化代码时被视为未使用。使用其字段的副本,例如在CPU寄存器中。