当前位置: 首页 > 知识库问答 >
问题:

如何使可完成的未来在完成后通过垃圾回收进行回收?

闾丘坚诚
2023-03-14

我基于java的CompletableFuture构建了一个任务链,它可能非常长。我的问题是CompletableFuture中的每一个任务都是一个内部类UniCompletion,它包含对源CompletableFuture的引用,因此不可能对已完成的CompletableForture进行垃圾收集。有没有办法避免内存泄漏?

这是一段可用于重现此错误的代码:

    public static void main(String... args) {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        AtomicReference<CompletableFuture<Integer>> future = new AtomicReference<>(CompletableFuture.completedFuture(0));
        IntStream.range(0, 100000000).forEach(i -> future.set(future.get().thenApplyAsync(ii -> ii + 1, executor)));
        future.get().get();
        executor.shutdown();
        executor.awaitTermination(10, TimeUnit.SECONDS);
    }

共有1个答案

臧令
2023-03-14

当我使用以下程序时,

import java.lang.ref.Cleaner;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.LockSupport;

public class CfGc {
    static final Cleaner CLEANER = Cleaner.create();
    static CompletableFuture<Integer> next(CompletableFuture<Integer> f) {
        Object[] status = { "not completed" };
        CLEANER.register(f, () -> System.out.println(status[0]+" future collected"));
        return f.whenComplete((i,t) -> {
                status[0] = t != null? t: i;
                LockSupport.parkNanos(500_000_000);
                System.out.println(status[0]+" completed, running gc()");
                System.gc();
                LockSupport.parkNanos(5_000_000);
                System.out.println(status[0]+" completed, gc() ran\n");
            }).thenApply(i -> i + 1);
    }
    public static void main(String[] args) {
        CompletableFuture<Integer> s = new CompletableFuture<>(), f = s;
        for(int i = 0; i < 6; i++) f = next(f);
        s.complete(1);
    }
}

它始终如一地在我的机器上打印

1 completed, running gc()
1 completed, gc() ran

2 completed, running gc()
2 completed, gc() ran

3 completed, running gc()
2 future collected
3 completed, gc() ran

4 completed, running gc()
3 future collected
4 completed, gc() ran

5 completed, running gc()
4 future collected
5 completed, gc() ran

6 completed, running gc()
5 future collected
6 completed, gc() ran

这表明,在评估未来的后续阶段时,未来是可以实现的,但在评估下一个阶段时,则无法实现。这不像整个链在最后一个阶段结束之前一直被引用。

只有第一个未来保持可访问性,这在链的顺序评估中是不可避免的,因为在第一个未来从方法调用的完整方法中,一切都发生在完整方法中。当我们将程序更改为

static final Cleaner CLEANER = Cleaner.create();
static CompletableFuture<Integer> next(CompletableFuture<Integer> f) {
    Object[] status = { "not completed" };
    CLEANER.register(f, () -> System.out.println(status[0]+" future collected"));
    return f.whenComplete((i,t) -> {
            status[0] = t != null? t: i;
            LockSupport.parkNanos(500_000_000);
            System.out.println(status[0]+" completed, running gc()");
            System.gc();
            LockSupport.parkNanos(5_000_000);
            System.out.println(status[0]+" completed, gc() ran\n");
        }).thenApplyAsync(i -> i + 1);
}
public static void main(String[] args) {
    CompletableFuture<Integer> s = new CompletableFuture<>(), f = s;
    for(int i = 0; i < 6; i++) f = next(f);
    s.complete(1);
    s = null;
    f.join();
}

它打印

1 completed, running gc()
1 completed, gc() ran

2 completed, running gc()
1 future collected
2 completed, gc() ran

3 completed, running gc()
2 future collected
3 completed, gc() ran

4 completed, running gc()
3 future collected
4 completed, gc() ran

5 completed, running gc()
4 future collected
5 completed, gc() ran

6 completed, running gc()
5 future collected
6 completed, gc() ran

在我的机器上,展示了当在完成过程中没有从堆栈框架中引用初始future时,它也会被垃圾收集。

这同样适用于我们使用

public static void main(String[] args) {
    CompletableFuture<Integer> f = CompletableFuture.supplyAsync(() -> 1);
    for(int i = 0; i < 6; i++) f = next(f);
    f.join();
}

无论next方法是使用然后使用Apply,还是使用然后使用applyAsync

 类似资料:
  • 如果我有一个(或多个)尚未启动,并且在该方法上有几个,方法。 垃圾收集器会移除所有这些吗? 如果在该链的末尾有一个< code > join()/< code > get() 也许我们需要更多关于连接上下文的信息()。 这种连接是线程中的最后一个命令,并且没有副作用。那么在这种情况下,线程仍然是活动的吗?- Java线程垃圾收集与否 无论如何,这是一个好主意,如果我确信(也许在尝试捕获中),那么将

  • 问题内容: 我很好奇嵌套函数的node.js模式如何与v8的垃圾收集器一起工作。这是一个简单的例子 如果restofprogram是长时间运行的,那是否不意味着str将永远不会被垃圾回收?我的理解是,使用结点,您最终会获得很多嵌套函数。如果在外部声明了restofprogram,是否会收集垃圾,因此str不能在范围内?这是推荐做法吗? 编辑 我不想使问题复杂化。那只是粗心,所以我修改了它。 问题答

  • 问题内容: 我有一个奇怪的疑问。我知道垃圾收集器有其自身的局限性。如果分配不正确,则可能导致应用程序以异常方式响应。 所以我的问题是,在每个活动结束时强制调用垃圾回收器()是良好的编程习惯吗? 更新资料 每个人都说调用system.gc()根本没有好处。然后,我想知道为什么它出现在这里。DVM将决定何时运行垃圾收集器。那么,该方法需要什么? 更新2 感谢社区的帮助。但老实说,我从此链接中获得了有关

  • 垃圾回收 我们对生产中花了很多时间来调整垃圾回收。垃圾回收的关注点与Java大致相似,尽管一些惯用的Scala代码比起惯用的Java代码会容易产生更多(短暂的)垃圾——函数式风格的副产品。Hotspot的分代垃圾收集通常使这不成问题,因为短暂的(short-lived)垃圾在大多情形下会被有效的释放掉。 在谈GC调优话题前,先看看这个Attila的报告,它阐述了我们在GC方面的一些经验。 Scal

  • 对于开发者来说,JavaScript 的内存管理是自动的、无形的。我们创建的原始值、对象、函数……这一切都会占用内存。 当我们不再需要某个东西时会发生什么?JavaScript 引擎如何发现它并清理它? 可达性(Reachability) JavaScript 中主要的内存管理概念是 可达性。 简而言之,“可达”值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的。 这里列出固有的可达值的

  • 垃圾收集,引用计数,显式分配 和所有的现代语言一样,OCaml提供垃圾收集器,所以你不用像C/C++一样显式地分配和释放内存。 JWZ在他的文章 "Java sucks" rant(Java蛋疼(怒)!): 第一个好家伙是Java没有 free()。其他的都没有所谓了。这几乎掩盖了所有的缺点,不管有多糟糕, 这个有点让后续文档基本都没有意义了,但是...(译注:但是啥大家自己看吧) OCaml的垃