逃避分析疑惑
Escape analysis doubts
我想我会做一个逃逸分析的小实验(Java 8、64 位服务器 JVM)。我想出了这个非常愚蠢的 "application",我在其中创建了很多 Address 对象(它们由邮政编码、街道、国家/地区和对象生成的时间戳组成。此外,Address 有一个 isOk() 方法,如果时间戳可以被 7 整除,则 returns 为真...)。
程序如下:
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 对其进行了概要分析,堆上没有地址对象,而它是 运行。整个应用程序在几秒钟内完成。
然而,当我这样重构它时:
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 循环块也不转义),那么为什么两个版本的行为如此不同?
你写“如果时间戳可以被 7 整除则 returns 为真”。这应该很明显,会发生什么。在您的第一个代码中:
boolean valid = true;
for (int i=0;i<1_000_000_000;i++) {
valid = valid && doGenerate();
}
return valid;
一旦时间戳碰巧不能被 7
整除,valid
将变为 false
。那么,根据&&
的工作方式,它会永远保持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;
一旦时间戳碰巧不能被 7
整除,valid
也会变成并保持 false
,但唯一短路的是调用isOk()
。无论 valid
.
的值如何,构造都会发生
原则上,Address
的构造可以在此处消除,但这需要栈上替换,因为它必须在循环运行时发生。目前尚不清楚这是否是这里的问题,但更重要的结论是,在 两种 情况下,我们都看到 EA 发生了,因为在第一种情况下,您没有调用包含分配(在未知但预期的少量调用之后)。
所以这两个例子是不等价的,不能得出逃逸分析的结论。
我想我会做一个逃逸分析的小实验(Java 8、64 位服务器 JVM)。我想出了这个非常愚蠢的 "application",我在其中创建了很多 Address 对象(它们由邮政编码、街道、国家/地区和对象生成的时间戳组成。此外,Address 有一个 isOk() 方法,如果时间戳可以被 7 整除,则 returns 为真...)。
程序如下:
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 对其进行了概要分析,堆上没有地址对象,而它是 运行。整个应用程序在几秒钟内完成。
然而,当我这样重构它时:
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 循环块也不转义),那么为什么两个版本的行为如此不同?
你写“如果时间戳可以被 7 整除则 returns 为真”。这应该很明显,会发生什么。在您的第一个代码中:
boolean valid = true;
for (int i=0;i<1_000_000_000;i++) {
valid = valid && doGenerate();
}
return valid;
一旦时间戳碰巧不能被 7
整除,valid
将变为 false
。那么,根据&&
的工作方式,它会永远保持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;
一旦时间戳碰巧不能被 7
整除,valid
也会变成并保持 false
,但唯一短路的是调用isOk()
。无论 valid
.
原则上,Address
的构造可以在此处消除,但这需要栈上替换,因为它必须在循环运行时发生。目前尚不清楚这是否是这里的问题,但更重要的结论是,在 两种 情况下,我们都看到 EA 发生了,因为在第一种情况下,您没有调用包含分配(在未知但预期的少量调用之后)。
所以这两个例子是不等价的,不能得出逃逸分析的结论。