我正在读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存储在堆栈而不是堆中,为什么?
答案 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
包含此类字段,我认为逸出分析无法删除该分配,因为它可能会更改原始行为。