2 回答
TA贡献2003条经验 获得超2个赞
代码示例用于说明引用文本的最后一句话,“请注意,仅当引用在堆栈上而不是存储在堆中时才允许进行这种优化”,您将其从解释中扯下来有点奇怪文本:
例如,考虑Finalizer Guardian模式:
class Foo {
private final Object finalizerGuardian = new Object() {
protected void finalize() throws Throwable {
/* finalize outer Foo object */
}
}
}
super.finalize如果子类覆盖并且finalize未显式调用super.finalize.
如果允许对存储在堆上的引用进行这些优化,那么 Java 编译器可以检测到该finalizerGuardian字段从未被读取,将其清空,立即收集对象,并尽早调用终结器。这与意图背道而驰:程序员可能想在 Foo 实例变得无法访问时调用 Foo 终结器。因此,这种转换是不合法的:只要外部类对象可访问,内部类对象就应该是可访问的。
所以代码示例说明了一个限制。规范中提到的“优化转换”包括在逃逸分析证明对象是纯本地的之后应用的对象标量化,换句话说,优化下的代码跨越对象的整个生命周期。
但它不需要这样的本地对象。正如规范已经提到的,优化的代码可以将对象的字段保留在 CPU 寄存器中,而无需重新读取它们,因此,不再需要保留对象引用。同样,仍然在范围内的引用变量可能未被使用。如果该引用是对某个对象的唯一引用,则将其从优化代码中删除允许更早地进行垃圾回收。
这两种情况仍然允许Foo实例被更早地消除或收集。这反过来将允许较早地收集 . 引用的对象(不再是)finalizerGuardian。但这并不能抵消这种限制的意图。规范将优化限制为不允许内部对象早于外部对象被收集,但将两者收集在一起没有问题,包括早于天真的预期。
通常,任意大的对象图可能会在单个垃圾收集周期中被收集,可能比天真预期的要早,甚至完全被优化掉。
TA贡献2039条经验 获得超7个赞
这种优化(逃逸分析)的一个经典例子是一个带有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);
现在很明显这new Point(i, i)是没用的,JIT 只是删除了这一行。
请注意,分配是在堆栈上,即在局部变量中。如果它在一个字段中,我们将无法进行优化,因为它存储在堆中。它是如何工作的。
关于您的代码被剪断:finalizerGuardian将始终在字段中(存储在堆中),而 JVM 对此分配无能为力。此外,如果Point上面示例中的类包含此类字段,我认为转义分析无法删除分配,因为它可能会改变原始行为。
添加回答
举报