JVM什么时候在堆栈上存储成员变量引用?

时间:2019-02-03 07:50:34

标签: java garbage-collection jvm

我正在读section 12.6.1 of Java SE specs,上面写着:

  

可以设计程序的优化转换,以将可到达的对象数量减少到少于天真的被认为可到达的对象数量。例如,Java编译器或代码生成器可能会选择设置将不再用于为null的变量或参数,以使此类对象的存储可能会尽快收回。

     

如果对象字段中的值存储在寄存器中,则会出现另一个示例。然后,程序可能会访问寄存器而不是对象,而不再访问对象。这意味着该对象是垃圾。请注意,仅当引用位于堆栈上而不存储在堆中时,才允许这种优化。

相关代码为:

class Foo {
    private final Object finalizerGuardian = new Object() {
        protected void finalize() throws Throwable {
            /* finalize outer Foo object */
        }
    }
} 

我的问题是,哪种JVM会将finalizerGuardian存储在堆栈而不是堆中,为什么?

2 个答案:

答案 0 :(得分:2)

该代码示例用于说明所引用文本的最后一句话,“ 请注意,只有在引用位于堆栈上而不是存储在堆中的情况下,才允许这种优化”,它是您将其从解释性文字中撕下来有点奇怪:

  

例如,考虑终结器守护者模式:

index.php/xmple/test/value1/value2
     

如果子类覆盖'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, 'rules' => [ 'xmple/test/<param1:\w+>/<param2:\w+>'=>'xmple/test' ], ], 且未显式调用 class Foo { private final Object finalizerGuardian = new Object() { protected void finalize() throws Throwable { /* finalize outer Foo object */ } } } ,则终结器监护器强制调用super.finalize

     

如果允许对存储在堆上的引用进行这些优化,则Java编译器可以检测到从未读取finalize字段,将其清空,立即收集对象并调用早定稿。这与意图背道而驰:程序员可能想在Foo实例变得不可访问时调用Foo终结器。因此,这种转换是不合法的:只要外部类对象是可到达的,内部类对象就应该是可到达的。

因此,代码示例说明了一个限制。规范中提到的“优化转换”包括在Escape Analysis证明对象纯粹是局部对象之后应用的对象标量化,换句话说,正在优化的代码跨越了对象的整个生命周期。

但是它不需要这样的本地对象。如规范所述,优化的代码可以将对象的字段保留在CPU寄存器中,而无需重新读取它们,因此,不再需要保留对象引用。同样,可能仍未使用范围内的参考变量。如果该引用是对对象的唯一引用,则从优化的代码中删除它可以进行较早的垃圾收集。

这两种情况仍将允许super.finalize实例被更早消除或收集。反过来,这将允许finalizerGuardian引用的对象的早期集合(不再有)。但这并不能抵消此限制的意图。规范将优化限制为不允许内部对象早于外部对象被收集,但是将两者收集在一起没有问题,包括早于天真的预期。

通常,任意大的对象图可能会在单个垃圾回收周期中被收集,可能早于天真的预期,甚至可能被完全优化。

答案 1 :(得分:1)

此类优化(转义分析)的经典示例是使用800px类的计算:

Point

在内联 class Point { double x; double y; public Point(final double x, final double y) { this.x = x; this.y = y; } double length() { return Math.sqrt(x * x + y * y); } static double calc() { double result = 0; for (int i = 0; i < 100; i++) { // this allocation will be optimized Point point = new Point(i, i); result += point.length(); } return result; } } 之后将不再需要,因为我们可以将所有字段提取为局部变量,例如

new

->

Point point = new Point(i, i);
double x = point.x;
double y = point.y;
result += Math.sqrt(x * x + y * y);

现在很明显Point point = new Point(i, i); double x = i; double y = i; result += Math.sqrt(x * x + y * y); 是无用的,JIT只需删除此行。

请注意,分配是在堆栈上,即在局部变量中。如果它在字段中,我们将无法进行优化,因为它存储在堆中。 那是如何工作的。

关于您的代码被截断的

new Point(i, i)将始终在字段中(存储在堆中),并且JVM无法执行此分配。而且,如果上述示例中的类finalizerGuardian包含此类字段,我认为逸出分析无法删除该分配,因为它可能会更改原始行为。