简体   繁体   中英

How to make a CompletableFuture be recycle by garbage collection when it's completed?

I built a task chain based on CompletableFuture of java, it could be very very long. My question is every task in CompletableFuture is an inner class UniCompletion , and it holds a reference to source CompletableFuture, so that it's impossible that completed CompletableFuture be garbage collected. Is there a way to avoid memory leaks for that?

Here is a piece of code that can be used to reproduce this error:

    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);
    }

When I use the follow program,

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);
    }
}

it consistently prints on my machine

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

This demonstrates that the future is reachable while its subsequent stage is evaluated, but not during the evaluation of the stage after next. It's not like the entire chain was kept referenced until the last stage finished.

Only the first future stays reachable, which is unavoidable in the sequential evaluation of the chain, as everything happens in the complete method invoked from the main method on the first future. When we change the program to

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();
}

it prints

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

on my machine, showing that the initial future can get garbage collected too when it is not referenced from a stack frame during the completion.

The same applies when we use

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

regardless of whether the next method uses thenApply or thenApplyAsync .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM