Non-intuitive disposal of objects from garbage

I am debugging a memory leak and had to dive into CompletainFuture. There is this piece of code (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;

      

The code itself is perfectly clear to me: apply a function that returns CompletionStage ( g

), create a relay that will eventually transfer the value to another CompletableFuture ( d

), and then return that other future ( d

). I see the following reference situation:

  • copy

    It refers to both d

    , and on g

    (and there is no magic in the constructor, only the destination fields in the constructor)
  • g

    links copy

  • d

    nothing links

Returns only d

, so in fact both g

, and copy

seem to me the internal variables of the method that (seemingly) never have to leave the method and eventually be gc'd. Both naive testing and the fact that it was written by trusted developers long ago tells me that I am wrong and something is missing. What is the reason why these objects are not removed from the garbage collection?

+3


source to share


1 answer


There is nothing in the cited code to prevent the garbage collection of these futures, and there is no need. This code refers to a scenario in which the first CompletableFuture

(instance this

) has completed and the CompletableFuture

one returned by the directly handled link function is not yet complete.

Now two scenarios are possible

  • Attempting to terminate. Then the code that will eventually complete the future will contain a reference to it, and upon completion, it will initiate the completion of the dependent stages (registered via g.push(copy)

    ). In this case, it is not necessary for the dependent rung to contain a link to its preliminary stage.

    This is a common pattern. If a chain exists x --will complete-→ y

    , the link will not be from y

    before x

    .

  • There is no other reference to this CompletableFuture

    instance g

    and g

    is not yet complete. In this case, it will never complete at all, and a link to g

    internally won't change that. This will only waste resources.

The following sample program illustrates this:

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

      



The first function passed to thenCompose

creates and returns a new unfinished one CompletableFuture

without any attempt to complete it, without saving or keeping any other reference to it. In contrast, the second function creates CompletableFuture

through supplyAsync

, providing Supplier

, which will return the value in a second.

On my system, it prints sequentially

future has been garbage collected
stage completed with newvalue

      

showing that an abandoned future will not be prevented by garbage collection, while another will be held at least until completion.

+2


source







All Articles