java - WeakReference 没有收集在大括号中?

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

测试失败。告诉 未删除!

这有效:

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

似乎大括号并没有影响弱点。

一些官方资源?

最佳答案

作用域是编译时的东西。它不决定对象在运行时的可达性,仅对实现细节有间接影响。

考虑以下测试变体:

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 :

A reachable object is any object that can be accessed in any potential continuing computation from any live thread.

在此示例中,潜在的连续计算无法访问该对象,因为不存在可访问该对象的此类后续计算。但是,无法保证特定 JVM 的垃圾收集器每次始终能够识别所有这些对象。事实上,即使 JVM 根本没有垃圾收集器也仍然符合规范,尽管可能不是本意。

代码优化对可达性分析产生影响的可能性也已明确 mentioned in the specification :

Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable. For example, a Java compiler or code generator may choose to set a variable or parameter that will no longer be used to null to cause the storage for such an object to be potentially reclaimable sooner.


那么技术上会发生什么?

如前所述,范围是编译时的事情。在字节码级别,离开花括号定义的范围没有任何效果。变量 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 的存储在 a 的范围结束后被 message 重用(这当然是特定于编译器的)并且即使在未优化的执行中,对象也会被收集。

请注意,关键方面是存储的实际覆盖。如果你使用

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++), 添加了一个附加操作,所以如果您认为使用short 可以提高效率,请注意它实际上是相反的。

关于java - WeakReference 没有收集在大括号中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47326757/

相关文章:

java - 为什么在使用 G1 GC 时推荐使用 Java 10?

c# - 正确使用 IDisposable 接口(interface)

java - 对字符串和字符串常量的弱引用

java - GC 会收集由 SoftReference 和 WeakReference 引用的对象吗?

java - 在 Grails 中显示日期/保存日期

java - 如何编写按年计算复利的方法?

java - 如何测试 Firebase 白名单电话号码

java - 如何监控垃圾回收以查看哪些内容被GC了?

java - 常量静态字段会导致 Web 应用程序中的内存泄漏吗?

c# - 事件如何导致 C# 中的内存泄漏以及弱引用如何帮助缓解这种情况?