在堆中移动对象时,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编译器生成代码时,无论如何都需要利用类型信息,并可以为垃圾收集器准备信息,但是它只会对某些称为安全点的点进行处理,在这些点中,生成的代码会检查是否存在未完成的垃圾收集。这意味着在这些点之间,数据不需要采用垃圾收集器能够理解的形式,并且优化的代码可以假定垃圾收集器在处理对象时不会重定位对象。

这也意味着在经过编译的优化代码中,可达性可能与简单解释执行中的完全不同,即,可能不存在未使用的变量,但是当从源代码角度来看,即使正在使用的对象也可以被视为未使用优化代码。其字段的副本,例如在CPU寄存器中。

09-30 12:16