What should happen to executor (it's local to that dead thread, no external references to it)? Should it be GCed or not?
The answer is more complex than "yes, it will be if no references are to it". It depends on whether or not the threads running in the ThreadPoolExecutor
are still running. This in turn depends on what type of TPE was created and whether or not the "long running tasks" that were submitted to it have finished.
For example, if the tasks have not finished then the threads will still be running.
Even if they have finish, if you had a TPE with core threads that did not set allowCoreThreadTimeOut(true)
then the threads will not stop. The JVM never garbage-collects a running thread since they are considered GC "roots":
... running threads are, by definition, immune to GC. The GC begins its work by scanning "roots", which are deemed always reachable; roots include global variables ("static fields" in Java-talk) and the stacks of all running threads ...
So the next question is if the threads have references back to the ThreadPoolExecutor
and I believe they do. The Worker
inner class is the Runnable
that is stored in thread.target
and is being executed by the Thread
so it can't be GC'd. Worker
is not static
so it has implied references to the outer ThreadPoolExecutor
instance. The run()
method is actually calling the ThreadPoolExecutor.runWorker()
method which references all of the task queues managed by the ThreadPoolExecutor
. So the running threads hold references back to the Worker
and the TPE so the garbage-collector can't collect the TPE.
For example, here's a typical stack frame of a running pool thread which references the TPE:
java.lang.Thread.sleep(Native Method)
com.j256.GcTester$1.run(GcTesteri.java:15)
java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
java.util.concurrent.FutureTask.run(FutureTask.java:266)
>> java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
>> java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
java.lang.Thread.run(Thread.java:748)
If, however, the thread-pool tasks have all finished and it has 0 core-threads or the core-threads have timed out, then there would be no Worker
threads associated with the ThreadPoolExecutor
. Then the TPE would be garbage collected because there were no references to it aside from cyclic ones that the GC is smart enough to detect.
Here's a little sample test program which demonstrates it. If there is 1 core thread then the TPE will never be shut down (through finalize()
) even after the worker thread exits after noticing that /tmp/x
file exists. This is true even though the main thread doesn't have a reference to it. If, however, there are 0 core threads then after the thread times out (here after 1 second after finishing the last task) the TPE will be collected.
public class GcTester {
public static void main(String[] args) {
int numCore = 1; // set to 0 to have it GC'd once /tmp/x file exists
ExecutorService pool =
new ThreadPoolExecutor(numCore, Integer.MAX_VALUE,
1, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()) {
protected void terminated() {
System.out.println(this + " terminated");
}
};
pool.submit(new Runnable() {
public void run() {
while (true) {
Thread.sleep(100); // need to handle exception here
if (new File("/tmp/x").exists()) {
System.out.println("thread exiting");
return;
}
}
}
});
pool = null; // allows it to be gc-able
while (true) {
Thread.sleep(1000); // need to handle exception here
System.gc(); // pull the big GC handle
}
}
}