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

时间:2017-06-13 19:48:18

标签: java garbage-collection completable-future

我正在调试内存泄漏,不得不深入到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;

代码本身对我来说非常清楚:应用一个返回CompletionStage(g)的函数,创建一个最终将值传递给另一个CompletableFuture(d)的中继,然后返回另一个未来(d)。我看到以下参考情况:

  • copy引用dg(构造函数中没有魔法,只有字段赋值)
  • g引用copy
  • d没有提及任何内容

仅返回d,因此,事实上,gcopy对我来说都是内部方法变量,(第一眼看上去)应该永远不会离开方法而是最终gc&#39; d。天真的测试以及很久以前由经过验证的开发人员编写的事实告诉我,我错了,错过了一些东西。是什么原因使这些对象从垃圾收集中被省略?

1 个答案:

答案 0 :(得分:2)

在引用的代码中,没有什么可以防止这些未来的垃圾收集,也没有必要。此代码适用于第一个CompletableFuturethis实例)已完成且直接评估的撰写函数返回的CompletableFuture尚未完成的情况。

现在,有两种可能的情况

  1. 正在进行完成尝试。然后,最终完成未来的代码将保留对它的引用,并且在完成时,它将触发从属阶段的完成(通过g.push(copy)注册)。在这种情况下,从属阶段不需要保留对其先决条件阶段的引用。

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

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

  3. 以下示例程序将说明这一点:

    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的第一个函数会创建并返回一个新的未完成的CompletableFuture,而不会尝试完成它,也不会保存或存储任何其他引用。相反,第二个函数通过CompletableFuture创建supplyAsync,提供Supplier,它将在一秒钟后返回一个值。

    在我的系统上,它一直打印

    future has been garbage collected
    stage completed with newvalue
    

    显示废弃的未来不会被垃圾收集阻止,而另一个将至少持续到完成。