1

I'm wondering the lifetime of Future object, which is not bound to a named variable.

I learned that Java adopts mark & sweep style garbage collection. In that case, any un-named object can be immediately deleted from heap. So I'm wondering if the Future might be swept out from memory even before the Runnable completes, or the memory might never be released.

Any information would be helpful, thanks!

class Main {
  void main() {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(() -> { return true; }); // not bind to variable
    Thread.sleep(1000);
  }
}
Yuki Hashimoto
  • 1,013
  • 7
  • 19
  • 1
    An object’s memory can be reused when it has no consequences for the application. Would you notice when the future’s memory got reclaimed? Obviously not Would it make a difference if you stored a reference to it in a variable when you don’t use it afterwards? Again, no, it makes no difference. By the way, `this` is a reference, even if you don’t declare it. But still, it makes no difference. See [Can java finalize an object when it is still in scope?](https://stackoverflow.com/q/24376768/2711488) and [finalize() called on strongly reachable object](https://stackoverflow.com/q/26642153/2711488) – Holger Dec 23 '20 at 13:03
  • 1
    @Holger [even better on the one that you already answered](https://stackoverflow.com/questions/58714980/rejectedexecutionexception-inside-single-executor-service) – Eugene Dec 23 '20 at 16:01

2 Answers2

2

I learned that Java adopts mark & sweep style garbage collection.

That is mostly incorrect. Most modern Java garbage collectors are NOT mark & sweep. They are mostly copying collectors that work by evacuating (copying) objects to a "to" space as they are marked. And most Java garbage collectors are also generational.

There is a lot of material published by Oracle about the Java garbage collectors and how they work. And there a good textbooks on the subject too.

In that case, any un-named object can be immediately deleted from heap.

Names have nothing to do with it. References are not names, and neither are variables. Java objects are deleted by the GC only if it finds that they are unreachable; i.e. if no code will never be able to find them again1. Furthermore they are not deleted immediately, or even (necessarily) at the next GC run.

So I'm wondering if the Future might be swept out from memory even before the Runnable completes, or the memory might never be released.

(That's a Callable rather than a Runnable. A Runnable doesn't return anything.)

The answer is no it won't.

The life cycle is something like this:

  1. You call submit passing a Callable.
  2. A CompletableFuture is created.
  3. The CompletableFuture and the Callable are added to the executor's queue.
  4. The CompletableFuture is returned to the caller. (In your case, the caller throws it away.)
  5. At a later point, a worker thread takes the Future and the Callable from the queue, executes the Callable.
  6. Then the worker thread calls complete on the Future to provide the result.
  7. Finally, something will typically call Future.get to obtain the result. (But not in your example.)

In order for the step 6. to work, the CompletableFuture must still be reachable. It won't be thrown away until all references are lost or discarded. Certainly not until after step 6 has completed.


Bottom line: a Java handles the Future just like it would any other (normal) object. Don't worry about it. If anything needs it, it won't disappear.


1 - Reachability is a bit more complicated when you consider, finalization and Reference types. But the same general principal applies. If any code could still see an object, it won't be deleted.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
1

"Submit" means to give something to someone, in this case you're giving a piece of code (in the form of a Callable) to the ExecutorService for later execution. In return, the method returns a Future object that will be updated with the result when it is done.

In order for the ExecutorService to update the Future object, it needs to hold on the Future object too, together with the code reference (the Callable).

Therefore, the ExecutorService maintains references to both the Callable object and the Future object until the job has been completed. Those references makes both objects reachable, preventing the objects from being garbage-collected.

Since your code discarded the returned Future object, the object will become eligible for GC as soon as the job completes, but not before that.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Thank you for your kind reply! I got the point, but then who retains the reference to `ExecutorService` ? Is it globally managed? Or, if I discard the reference to the executor, will it be garbage-collected? – Yuki Hashimoto Dec 23 '20 at 06:10
  • 1
    @YukiHashimoto Yes, even after `main()` returns, the `ExecutorService` is referenced by the running thread of the service. That won't go away until you `shutdown()` the service. – Andreas Dec 23 '20 at 06:16
  • Thanks for your kind and quick response! I'm not sure why and when the `executorService` is gc-ed. I found a similar question at https://stackoverflow.com/questions/24953978/does-an-executorservice-get-garbage-collected-when-out-of-scope , but the answer does not give me further insight. I guess the executing thread does not refer to the `executorService` obj, because of the dependency direction: `ExecutorService` depends on `Thread`, not the reverse. – Yuki Hashimoto Dec 23 '20 at 06:57
  • 2
    There will be references in both directions. The worker thread needs a reference to the `ExecutorService` data structures do that it can get the next work item from the queue. The `shutdown()` method causes the worker thread(s) to eventually commit suicide, and the `ExecutorService` remains reachable until that happens. – Stephen C Dec 23 '20 at 07:50
  • Thank you for your answer! But I failed to find any reference from `thread` obj to `executorService` in case of `ThreadPoolExecutor`, and I'm not sure when `executorService.shutdown()` is called if the lifetime of `executorService` is longer than the threads. I guess `executorService` has the shortest lifetime among `executorService`, `thread`, and `future`, and the finalizer of `executorService` will invoke `executorService.terminate()`, then the `thread` is released at the same time. Is this correct? – Yuki Hashimoto Dec 24 '20 at 01:46
  • @YukiHashimoto The `Runnable` that the given to the thread is an instance of class `Worker`, which is an **inner** class of `ThreadPoolExecutor`, so it has an implicit `ThreadPoolExecutor.this` reference to the `ThreadPoolExecutor`. As long as the thread keeps running, the `ThreadPoolExecutor` is reachable from the thread. Since you never call `executor.shutdown()`, the thread and the `ThreadPoolExecutor` will live forever, i.e. until you kill the application. – Andreas Dec 24 '20 at 04:53