If you create a thread and never call its start() method,
then later null your reference to that thread, it will
never be garbage collected. The reason is that the
java.lang.Thread class adds the thread to the thread
group in the init() method, which is called from the
constructor. It removes the thread from the thread
group in the exit() method, which is only called if the
thread is run. So if the thread is never run, the thread
group still has a reference to the Thread and it will never
be garbage collected. And of course any objects which the
thread has references to will in turn not be garbage collected.
The following code illustrates this.
class ThreadLeak {
static MyThread t;
public static void main(String[] args) {
if ((args.length > 0) && args[0].equals("run")) {
System.out.println("Creating and running 10,000 threads...");
for(int i = 0; i < 10000; i++) {
t = new MyThread(true);
}
} else {
System.out.println("Creating 10,000 threads, but not running them...");
for(int i = 0; i < 10000; i++) {
t = new MyThread(false);
}
}
System.out.println("Running garbage collector...");
System.gc();
System.out.println("Done. Heap size is " +
(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()));
}
}
class MyThread implements Runnable {
Thread t;
public MyThread(boolean bRun) {
t = new Thread(this, "MyThread");
if (bRun) {
t.start();
}
}
public void run() {
/* NO-OP */
}
}
This creates 10,000 threads and optionally runs them.
If you run this code with "java ThreadLeak", the threads are
created but not run, and you will see that the used heap
memory is well over 1MB. If you run this code with
"java ThreadLeak run", the threads are created and run,
and you will see that the heap is much smaller because
the threads have not been leaked. This is a rather contrived
example for simplicity, but in reality even one leaked thread
can cause a large memory leak if it has references to other
objects.
How to fix this is debatable. If you wait until the
start() method to add the thread to the thread group, then
any call to the thread's enumerate() method will not list
the thread until it is run. This might be an acceptable quirk.
Or you could add a new public API to remove the thread from the
the thread group in the even that it is never used
(currently, ThreadGroup.remove() is a private method).
Or at the very least, document this behavior so people can avoid
the memory leak by waiting until the thread needs to be run
before creating it.
(Review ID: 48015)
- duplicates
-
JDK-4203988 (thread) Threads not garbage collected if constructor fails with an exception
- Closed
-
JDK-4521146 (thread) OutOfMemoryError when start() is never invoked on a Thread
- Closed
-
JDK-4684790 URLClassLoader will not GC if loaded class registers shutdown hook
- Closed
-
JDK-4729328 Member assignment in finalize breaks garbage collection
- Closed
-
JDK-4902813 (thread) Thread is not garbage collected if it is not started
- Closed
-
JDK-4508232 (thread) Unborn Threads are not garbage collected
- Closed
-
JDK-4495488 Document behaviour described in Bug #4197876
- Closed
- relates to
-
JDK-4197876 If start method is never called on a thread, that thread is leaked
- Closed