从垃圾收集中驱逐非直观的对象

Non-intuitive object eviction from garbage collection

我正在调试内存泄漏,不得不深入研究 CompletableFuture 的内部结构。有这段代码(CompletableFuture.uniComposeStage):

CompletableFuture<V> g = f.apply(t).toCompletableFuture();
...
CompletableFuture<V> d = new CompletableFuture<V>();
UniRelay<V> copy = new UniRelay<V>(d, g);
g.push(copy);
copy.tryFire(SYNC);
return d;

代码本身对我来说很清楚:应用一个 returns CompletionStage (g) 的函数,创建一个中继,最终将价值转移到另一个 CompletableFuture (d ),然后 return 这是另一个未来 (d)。我看到以下参考情况:

只有 d 是 returned,所以,事实上,gcopy 对我来说都是内部方法变量,(乍一看)永远不应该离开方法并最终被 gc'。幼稚的测试和很久以前由经过验证的开发人员编写的事实都表明我错了并且遗漏了一些东西。是什么原因导致这些对象被垃圾收集遗漏?

在引用的代码中,没有任何东西阻止对这些期货进行垃圾回收,也没有必要。这段有问题的代码适用于第一个CompletableFuturethis实例)已经完成而直接评估的compose函数的CompletableFuture return还没有完成的场景还。

现在,有两种可能的情况

  1. 正在进行完成尝试。然后,最终将完成 future 的代码将持有对它的引用,并且在完成时,它将触发依赖阶段的完成(通过 g.push(copy) 注册)。在这种情况下,依赖阶段不需要保存对其前提阶段的引用。

    这是一般模式。如果存在链x --will complete-→ y,则不会有从yx的引用。

  2. 没有其他参考 CompletableFuture 实例 g 并且 g 尚未完成。在这种情况下,它永远不会完成并且在内部持有对 g 的引用不会改变这一点。那只会浪费资源。

下面的示例程序将对此进行说明:

public static void main(String[] args) throws Throwable {
    ReferenceQueue<Object> discovered = new ReferenceQueue<>();
    Set<WeakReference<?>> holder = new HashSet<>();

    CompletableFuture<Object> initial = CompletableFuture.completedFuture("somevalue");

    CompletableFuture<Object> finalStage = initial.thenCompose(value -> {
        CompletableFuture<Object> lost = new CompletableFuture<>();
        holder.add(new WeakReference<>(lost, discovered));
        return lost;
    });
    waitFor(finalStage, holder, discovered);
    finalStage = initial.thenCompose(value -> {
        CompletableFuture<Object> saved = CompletableFuture.supplyAsync(()-> {
            LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
            return "newvalue";
        });
        holder.add(new WeakReference<>(saved, discovered));
        return saved;
    });
    waitFor(finalStage, holder, discovered);
}
private static void waitFor(CompletableFuture<Object> f, Set<WeakReference<?>> holder,
                    ReferenceQueue<Object> discovered) throws InterruptedException {
    while(!f.isDone() && !holder.isEmpty()) {
        System.gc();
        Reference<?> removed = discovered.remove(100);
        if(removed != null) {
            holder.remove(removed);
            System.out.println("future has been garbage collected");
        }
    }
    if(f.isDone()) {
        System.out.println("stage completed with "+f.join());
        holder.clear();
    }
}

传递给 thenCompose 的第一个函数创建并 returns 一个新的未完成的 CompletableFuture 没有任何尝试来完成它,不持有或存储任何其他对它的引用。相比之下,第二个函数通过 supplyAsync 创建 CompletableFuture 提供 Supplier ,它将在一秒钟后 return 一个值。

在我的系统上,它始终打印

future has been garbage collected
stage completed with newvalue

显示被遗弃的 future 不会被垃圾收集阻止,而另一个将至少保留到完成。