简体   繁体   中英

Availability of objects for garbage collection used inside CompletableFuture.thenAccept

I am trying to execute an async call from a method using CompletableFuture . On completion of that task, I am trying to print values of the object DummyObject which are local to the method calling Async call.

I want to know how is it working? The thread( myThread ) is dead after 3 seconds and DummyObject is out of scope, still the Async callback inside thenAccept is printing correct values. Is the thread getting a lock on DummyObject? Or something else is happening?

Please note that I am just trying to simulate a scenario. So I am using Thread.stop() .

Edit - Question is about how thenAccept Consumer is handling the scope? Please keep answers relevant to that.

Output of the below program:

Starting Async stuff
Thread Alive?  false
Reached Main end and waiting for 8 more seconds
James T. Kirk
United Federation of Planets

AsyncTest.java

public class AsyncTest {

    public static void main(String args[]) {

        Thread myThread = new Thread() {
            @Override
            public void run() {
                DummyObject dummyObj = new DummyObject();
                dummyObj.setObjectName("James T. Kirk");
                dummyObj.setObjectNationality("United Federation of Planets");
                System.out.println("Starting Async stuff");
                new AsyncTaskExecuter().executeAsync().thenAccept(taskStatus -> {
                    if(taskStatus.booleanValue()) {
                        System.out.println(dummyObj.getObjectName());
                        System.out.println(dummyObj.getObjectNationality());
                    }
                });
            }
        };

        myThread.start();
        try {
            Thread.sleep(3 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myThread.stop();
        System.out.println("Thread Alive?  "+ myThread.isAlive());

        System.out.println("Reached Main end and waiting for 8 more seconds");

        try {
            Thread.sleep(8 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class AsyncTaskExecuter {
    public CompletableFuture<Boolean> executeAsync() {
        return  (CompletableFuture<Boolean>) CompletableFuture.supplyAsync(this::theTask);
    }

    public Boolean theTask() {
        try {
            Thread.sleep(6 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }
}

class DummyObject {
    private String objectName;
    private String objectNationality;
    // getters and setters
}

First thing. Please don't use stop() method. It is deprecated. The thread dies on its own once it finishes executing its run method.

Now to answer your question, when you create a CompletableFuture, it also creates a thread in java's ForkJoinCommonPool and executes it there sometime in future. So effectively in your application you are creating 3 threads. So your object cannot die as it will be used by one of the thread.

  1. Main Thread
  2. My-Thread
  3. And final one is the one created by CompletableFuture. (This will use the dummy object)

So even if thread 1, 2 die. Thread 3 still will execute.

在此处输入图片说明

PS I have given name to your thread as Thread-MyThread to show you the threads. You can give name to your threads by calling Thread's constructor which takes name for the thread as parameter. The tool I am using is JVisualVM.

At the point you are talking about, the dummyObj variable is still in scope within the lambda that you passed to thenAccept .

In fact, the stack frame with the original variable will have gone away. However, the Java compiler arranges that the object that represents the lambda has a copy of the original variable's value (ie the reference to the DummyObject instance) saved in a read-only synthetic variable.

(This is the reasoning behind the restriction that a lambda can only use a local variable in the enclosing method if the variable is effectively final . If a variable is effectively final, its value can be copied to another final variable without any risk of inconsistencies due to the variable(s) changing.)

Anyhow, the lambda uses the DummyObject via the synthetic variable. And the DummyObject instance will remain reachable as long as the lambda is reachable.

If the local variable is of primitive type, what is stored on stack is its value. In case of reference type local variables the reference is also stored on stack but the actual object is stored in heap.

So in case of using local variable inside an anonymous inner class(lambda in your code) the copy of what is held in stack is taken to anonymous class, in case of primitive type variable the copy is the value of variable, in case of reference type variable the copy is the reference itself. Hence after the stack frame is gone the anonymous class either has the copy of primitive type or the copy of actual reference the value of which is stored in heap, and while there is a reference to an object garbage collector will not recycle it.

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