JDK-4197876 : If start method is never called on a thread, that thread is leaked
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 1.1.5,1.2.1,1.2.2
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: solaris_8,windows_98,windows_nt
  • CPU: x86,sparc
  • Submitted: 1998-12-16
  • Updated: 1999-04-23
  • Resolved: 1999-04-23
Related Reports
Duplicate :  
Relates :  
Description

Name: clC74495			Date: 12/16/98


This occurs in all versions of the JDK, from 1.1.5 through 1.2.
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)
======================================================================

Comments
WORK AROUND Name: clC74495 Date: 12/16/98 The work around is of course to only create threads when you know you are going to run them. But it seems valid to create a thread that never gets run, for instance if you have a thread that will get kicked off by some GUI action that the user never uses. ====================================================================== If a program must create a thread that may never run, it should write the thread's run method in such a fashion that it can be run harmlessly. For instance, the run method could look like this: public void run() { if not (suicidal) { // do whatever it is your were planning to do } } The suicidal variable could be set appropriately prior to running the thread. michael.mccloskey@eng 1999-04-23
23-04-1999

EVALUATION Unfortunately, this situtation cannot be remedied without changing the threading API: even after one has discarded the reference to an unstarted thread, it is possible to obtain this reference by iterating over its ThreadGroup. Thus, the runtime would not be within its rights in garbage collecting the thread: it could still be run at a later date. michael.mccloskey@eng 1999-04-23 We will re-examine this issue. Bug 4533087 has been created to track it. ###@###.### 2001-11-29
23-04-1999