我正在调试内存泄漏,不得不深入到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
引用d
和g
(构造函数中没有魔法,只有字段赋值)g
引用copy
d
没有提及任何内容仅返回d
,因此,事实上,g
和copy
对我来说都是内部方法变量,(第一眼看上去)应该永远不会离开方法而是最终gc&#39; d。天真的测试以及很久以前由经过验证的开发人员编写的事实告诉我,我错了,错过了一些东西。是什么原因使这些对象从垃圾收集中被省略?
答案 0 :(得分:2)
在引用的代码中,没有什么可以防止这些未来的垃圾收集,也没有必要。此代码适用于第一个CompletableFuture
(this
实例)已完成且直接评估的撰写函数返回的CompletableFuture
尚未完成的情况。
现在,有两种可能的情况
正在进行完成尝试。然后,最终完成未来的代码将保留对它的引用,并且在完成时,它将触发从属阶段的完成(通过g.push(copy)
注册)。在这种情况下,从属阶段不需要保留对其先决条件阶段的引用。
这是一般模式。如果存在链x --will complete-→ y
,则y
与x
之间不会有引用。
没有其他对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
的第一个函数会创建并返回一个新的未完成的CompletableFuture
,而不会尝试完成它,也不会保存或存储任何其他引用。相反,第二个函数通过CompletableFuture
创建supplyAsync
,提供Supplier
,它将在一秒钟后返回一个值。
在我的系统上,它一直打印
future has been garbage collected
stage completed with newvalue
显示废弃的未来不会被垃圾收集阻止,而另一个将至少持续到完成。