WeakReference没有收集在大括号中?

时间:2017-11-16 10:06:11

标签: java garbage-collection weak-references linkage

此操作失败

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!";
}

似乎大括号不会影响弱点。

一些官方资源?

1 个答案:

答案 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进行byteshortcharint计算(如您所知,例如在尝试编写shortVariable = shortVariable + 1;时)并要求它将结果值剪切为short(当您使用shortVariable++时仍会隐式发生),添加附加操作,如果您认为,使用{{1提高效率,注意它实际上是相反的。