从垃圾收集中驱逐非直观的对象
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
)。我看到以下参考情况:
copy
引用了 d
和 g
(构造函数中没有魔法,只有字段赋值)
g
引用 copy
d
未引用任何内容
只有 d
是 returned,所以,事实上,g
和 copy
对我来说都是内部方法变量,(乍一看)永远不应该离开方法并最终被 gc'。幼稚的测试和很久以前由经过验证的开发人员编写的事实都表明我错了并且遗漏了一些东西。是什么原因导致这些对象被垃圾收集遗漏?
在引用的代码中,没有任何东西阻止对这些期货进行垃圾回收,也没有必要。这段有问题的代码适用于第一个CompletableFuture
(this
实例)已经完成而直接评估的compose函数的CompletableFuture
return还没有完成的场景还。
现在,有两种可能的情况
正在进行完成尝试。然后,最终将完成 future 的代码将持有对它的引用,并且在完成时,它将触发依赖阶段的完成(通过 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
的第一个函数创建并 returns 一个新的未完成的 CompletableFuture
没有任何尝试来完成它,不持有或存储任何其他对它的引用。相比之下,第二个函数通过 supplyAsync
创建 CompletableFuture
提供 Supplier
,它将在一秒钟后 return 一个值。
在我的系统上,它始终打印
future has been garbage collected
stage completed with newvalue
显示被遗弃的 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;
代码本身对我来说很清楚:应用一个 returns CompletionStage (g
) 的函数,创建一个中继,最终将价值转移到另一个 CompletableFuture (d
),然后 return 这是另一个未来 (d
)。我看到以下参考情况:
copy
引用了d
和g
(构造函数中没有魔法,只有字段赋值)g
引用copy
d
未引用任何内容
只有 d
是 returned,所以,事实上,g
和 copy
对我来说都是内部方法变量,(乍一看)永远不应该离开方法并最终被 gc'。幼稚的测试和很久以前由经过验证的开发人员编写的事实都表明我错了并且遗漏了一些东西。是什么原因导致这些对象被垃圾收集遗漏?
在引用的代码中,没有任何东西阻止对这些期货进行垃圾回收,也没有必要。这段有问题的代码适用于第一个CompletableFuture
(this
实例)已经完成而直接评估的compose函数的CompletableFuture
return还没有完成的场景还。
现在,有两种可能的情况
正在进行完成尝试。然后,最终将完成 future 的代码将持有对它的引用,并且在完成时,它将触发依赖阶段的完成(通过
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
的第一个函数创建并 returns 一个新的未完成的 CompletableFuture
没有任何尝试来完成它,不持有或存储任何其他对它的引用。相比之下,第二个函数通过 supplyAsync
创建 CompletableFuture
提供 Supplier
,它将在一秒钟后 return 一个值。
在我的系统上,它始终打印
future has been garbage collected
stage completed with newvalue
显示被遗弃的 future 不会被垃圾收集阻止,而另一个将至少保留到完成。