public void testWeak() throws Exception {
waitGC();
{
Sequence a = Sequence.valueOf("123456789");
assert Sequence.used() == 1;
a.toString();
}
waitGC();
}
private void waitGC() throws InterruptedException {
Runtime.getRuntime().gc();
short count = 0;
while (count < 100 && Sequence.used() > 0) {
Thread.sleep(10);
count++;
}
assert Sequence.used() == 0: "Not removed!";
}
测试失败。告诉Not removed!
。
public void testAWeak() throws Exception {
waitGC();
extracted();
waitGC();
}
private void extracted() throws ChecksumException {
Sequence a = Sequence.valueOf("123456789");
assert Sequence.used() == 1;
a.toString();
}
private void waitGC() throws InterruptedException {
Runtime.getRuntime().gc();
short count = 0;
while (count < 100 && Sequence.used() > 0) {
Thread.sleep(10);
count++;
}
assert Sequence.used() == 0: "Not removed!";
}
似乎大括号不会影响弱点。
一些官方资源?
答案 0 :(得分:1)
Scope是一个编译时间的东西。它不是在运行时确定对象的可达性,而是由于实现细节而具有间接影响。
考虑以下测试的变体:
static boolean WARMUP;
public void testWeak1() throws Exception {
variant1();
WARMUP = true;
for(int i=0; i<10000; i++) variant1();
WARMUP = false;
variant1();
}
private void variant1() throws Exception {
AtomicBoolean track = new AtomicBoolean();
{
Trackable a = new Trackable(track);
a.toString();
}
if(!WARMUP) System.out.println("variant1: "
+(waitGC(track)? "collected": "not collected"));
}
public void testWeak2() throws Exception {
variant2();
WARMUP = true;
for(int i=0; i<10000; i++) variant2();
WARMUP = false;
variant2();
}
private void variant2() throws Exception {
AtomicBoolean track = new AtomicBoolean();
{
Trackable a = new Trackable(track);
a.toString();
if(!WARMUP) System.out.println("variant2: "
+(waitGC(track)? "collected": "not collected"));
}
}
static class Trackable {
final AtomicBoolean backRef;
public Trackable(AtomicBoolean backRef) {
this.backRef = backRef;
}
@Override
protected void finalize() throws Throwable {
backRef.set(true);
}
}
private boolean waitGC(AtomicBoolean b) throws InterruptedException {
for(int count = 0; count < 10 && !b.get(); count++) {
Runtime.getRuntime().gc();
Thread.sleep(1);
}
return b.get();
}
在我的机器上打印:
variant1: not collected
variant1: collected
variant2: not collected
variant2: collected
如果无法重现它,则可能需要增加预热迭代次数。
它演示的内容:a
是否在范围(变体2)中是否(变体1)无关紧要,在任何一种情况下,对象都没有在冷执行中收集,而是在收集后收集换句话说,在优化器启动后的预热迭代次数。
正式地,a
始终总是有资格在我们调用waitGC()
时进行垃圾回收,因为从这一点开始它没有被使用。这是how reachability is defined:
可达对象是任何可以从任何活动线程继续计算中访问的对象。
在此示例中,潜在的继续计算无法访问该对象,因为不存在将访问该对象的后续计算。但是,没有保证特定JVM的垃圾收集器始终能够在每次都识别所有这些对象。事实上,即使没有垃圾收集器的JVM仍然符合规范,但可能不是意图。
代码优化对可达性分析产生影响的可能性也明确mentioned in the specification:
可以设计优化程序的转换,以减少可达到的对象数量,使其少于可以被认为可达的对象数量。例如,Java编译器或代码生成器可以选择将不再用于
null
的变量或参数设置为使得此类对象的存储可能更快地回收。
那么技术上会发生什么?
如上所述,范围是编译时的事情。在字节码级别,保留由花括号定义的范围无效。变量a
超出范围,但它在堆栈帧中的存储仍然存在,保留引用直到被另一个变量覆盖或者直到方法完成。编译器可以自由地将存储重用于另一个变量,但在此示例中,不存在此类变量。因此,上述示例的两个变体实际上生成相同的字节码。
在未优化的执行中,堆栈帧中仍然存在的引用被视为阻止对象集合的引用。在优化执行中,引用仅保持到最后一次实际使用。对其字段进行内联可以允许更早地收集它,直到它在构造之后立即收集(或者根本没有构造,如果它没有finalize()
方法)。极端是finalize() called on strongly reachable object in Java 8 ......
当您插入另一个变量时,事情会发生变化,例如
private void variant1() throws Exception {
AtomicBoolean track = new AtomicBoolean();
{
Trackable a = new Trackable(track);
a.toString();
}
String message = "variant1: ";
if(!WARMUP) System.out.println(message
+(waitGC(track)? "collected": "not collected"));
}
然后,在a
的范围结束后(当然,特定于编译器)message
重用a
的存储,并且即使在未优化的执行中也会收集对象
请注意,关键方面是实际覆盖存储。如果你使用
private void variant1() throws Exception {
AtomicBoolean track = new AtomicBoolean();
{
Trackable a = new Trackable(track);
a.toString();
}
if(!WARMUP)
{
String message = "variant1: "
+(waitGC(track)? "collected": "not collected");
System.out.println(message);
}
}
message
变量使用与a
相同的存储空间,但其分配仅在调用waitGC(track)
后发生,因此您获得相同的未优化执行行为与原始变体一样。
顺便说一句,不要将short
用于本地循环变量。 Java始终使用int
进行byte
,short
,char
和int
计算(如您所知,例如在尝试编写shortVariable = shortVariable + 1;
时)并要求它将结果值剪切为short
(当您使用shortVariable++
时仍会隐式发生),添加附加操作,如果您认为,使用{{1提高效率,注意它实际上是相反的。