In one of our services, someone added such (simplified) a piece of code:
public class DeleteMe {
public static void main(String[] args) {
Delet
You wrote
To be frank initially I though that
ExecutorService
is GC-ed - reachability and scope are different things and GC is allowed to clear anything which is not reachable; but there is aFuture<?>
that will keep a strong reference to that service, so I excluded this.
But this is actually a very plausible scenario, which is described in JDK-8145304. In the bug report's example the ExecutorService
is not held in a local variable, but a local variable does not prevent garbage collection per se.
Note that the exception message
Task java.util.concurrent.FutureTask@3148f668 rejected from
java.util.concurrent.ThreadPoolExecutor@6e005dc9[Terminated,
pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
supports this, as the state of ThreadPoolExecutor@6e005dc9
is specified as Terminated
.
The assumption that futures hold a reference to their creating ExecutorService
is wrong. The actual type depends on the service implementation, but for the common ones, it will be an instance of FutureTask which has no reference to an ExecutorService
. It's also visible in the exception message that this applies to your case.
Even if it had a reference, the creator would be the actual ThreadPoolExecutor
, but it is the wrapping FinalizableDelegatedExecutorService
instance which gets garbage collected and calls shutdown()
on the ThreadPoolExecutor
instance (Thin wrappers are generally good candidates for premature garbage collection in optimized code which just bypasses the wrapping).
Note that while the bug report is still open, the problem is actually fixed in JDK 11. There, the base class of FinalizableDelegatedExecutorService
, the class DelegatedExecutorService
has an execute
implementation that looks like this:
public void execute(Runnable command) {
try {
e.execute(command);
} finally { reachabilityFence(this); }
}