I have created a thread which in turn creates a ThreadPoolExecutor
and submits some long running tasks to it. At some point, the original thread dies due to unh
Threads are so called GC roots. This means among other things, that a running (or an unstarted) thread can't be collected. It also means that objects that are being referenced from those threads can't be collected, which is why you can do things like new Thread(new MyRunnable()).start()
, or have threadpools running without you having any reference to them.
If the thread is a daemon, it can stop automatically if all other non-daemon threads have stopped. You can have threadpools with daemon threads, but the best thing is to make sure that things are cleaned up properly (i.e. an exception shouldn't kill the thread that's supposed to eventually stop and cleanup the threadpool).
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
}
}
}