当在堆中移动对象时,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
指令将什么放入变量中。
validator 可以做到这一点,甚至是强制性的,垃圾收集器或 JVM 的任何支持代码也可以做到这一点。一个实现甚至可能有一个分析代码,保存第一次分析的类型信息,这将是验证。
但是,即使每次垃圾收集器需要时都重建这些信息,开销也不会是天文数字。垃圾收集器仅定期运行,并且仅需要当前执行的方法的此信息。这只是解释执行的全部内容。
当 JIT 编译器生成代码时,它无论如何都需要利用类型信息,并且可以为垃圾收集器准备信息,但它只会针对生成的代码检查的称为“安全点”的某些点执行此操作是否有未完成的垃圾收集。这意味着在这些点之间,数据不需要采用垃圾收集器理解的形式,并且优化的代码可能假设垃圾收集器在处理对象时不会重新定位对象。
这还意味着,在已编译的优化代码中,可达性可能与简单的解释执行完全不同,即可能不存在未使用的变量,但即使从源代码的角度来看,正在使用的对象在优化时也可能被视为未使用。代码使用其字段的副本,例如在CPU寄存器中。关于java - 垃圾收集器如何更新推送到操作数堆栈的引用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60511540/