2

我想我会做一个逃逸分析的小实验(Java 8、64 位服务器 JVM)。我想出了这个非常愚蠢的“应用程序”,我在其中创建了很多 Address 对象(它们由邮政编码、街道、国家和生成对象的时间戳组成。此外,Address 有一个 isOk() 方法,它返回如果时间戳可以被 7 整除,则为 true...)。

所以这是程序:

private boolean generate() {
    boolean valid = true;
    for (int i=0;i<1_000_000_000;i++) {
        valid = valid && doGenerate();
    }

    return valid;
}

private boolean doGenerate() {
    long timeGenerated = System.currentTimeMillis();
    Address address = new Address(1021, "A Street", "A country", timeGenerated);
    return address.isOk();
}

到目前为止一切顺利,我使用 jVisualVM 对其进行了分析,在它运行时堆上没有 Address 对象。整个应用程序在几秒钟内完成。

但是,当我像这样重构它时:

private boolean generate() {
    boolean valid = true;
    for (int i=0;i<1_000_000_000;i++) {
        long timeGenerated = System.currentTimeMillis();
        Address address = new Address(1021, "A Street", "A country", timeGenerated);
        valid = valid && address.isOk();
    }

    return valid;
}

Baaang,没有逃逸分析,每个 Address 对象最终都分配在堆上,垃圾回收周期很长。为什么会这样?我的意思是,Address 实例不会以任何一种方式转义(在第二个版本中,Address 对象的范围更窄,它们不会转义方法,甚至不会转义 for 循环块),那么为什么两个版本的行为如此不同呢?

4

1 回答 1

4

你写“如果时间戳能被 7 整除,则返回 true”。这应该很明显,会发生什么。在您的第一个代码中:

boolean valid = true;
for (int i=0;i<1_000_000_000;i++) {
    valid = valid && doGenerate();
}
return valid;

validfalse一旦时间戳碰巧不能被 整除,就会变成7. 然后,根据工作方式&&,它将false永远存在,并且由于&&短路,doGenerate()因此承担分配的方法将永远不会被再次调用。

相反,在您的第二个变体中

boolean valid = true;
for (int i=0;i<1_000_000_000;i++) {
    long timeGenerated = System.currentTimeMillis();
    Address address = new Address(1021, "A Street", "A country", timeGenerated);
    valid = valid && address.isOk();
}
return valid;

validfalse一旦时间戳碰巧不能被 整除,它也将变为并保持不变7,但唯一短路的是 的调用isOk()。无论 的值如何,构造都会发生valid

原则上,这里的构造Address可以被消除,但这需要堆栈替换,因为它必须在循环运行时发生。不清楚这是否是这里的问题,但更重要的结论是,在这两种情况下,我们都看到 EA 发生,因为在第一种情况下,您没有调用包含分配的方法(在一个未知但预期很小的数字之后调用)。

所以这两个例子是不等价的,不能得出关于逃逸分析的结论。

于 2015-10-19T14:27:27.020 回答