JDK-4287000 : instance of class that extends Thread not gc'd if start() not called
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 1.2.1,1.2.2
  • Priority: P2
  • Status: Closed
  • Resolution: Duplicate
  • OS: solaris_8,windows_98
  • CPU: x86,sparc
  • Submitted: 1999-11-02
  • Updated: 1999-11-05
  • Resolved: 1999-11-05
Related Reports
Duplicate :  
Description
Customer has created a new class that will run in it's own Thread. He has done this by extending the Thread class instead of by implementing the runnable interface.
Instances of this class are then used to handle client requests and the application determines whether to call the instances start() method which will have the connection handled in a seperate Thread or the instances run() method which will have the connection handled in the current Thread. 
Using the second method, the instance is never gc'd even when completely dereferenced by the code that called it. This is due to the fact that the Threading system has a reference to the instance held as an element of a ThreadGroup. This reference is not lost until the Thread is started and then stopped.


Comments
WORK AROUND -- use the class given here in place of Thread -- import java.lang.Thread; import java.lang.ThreadGroup; import java.lang.Runnable; // // new "helper" class which delegates to a Thread object // // -- abstract class, concrete subclasses must implement "run()" // /** * * <i>RunnableDelegate</i> is intended to support programming styles in * which the <code>run</code> method of a <code>Thread</code> (or a * user defined subclass of <code>Thread</code>) object is called * directly, rather than calling the <code>start</code> method of the * <code>Thread</code>. This has the effect of causing the <code>run></code> * method of the <code>Thread</code> to be executed in the currently * executing thread, rather than in a new thread of execution. * * The problem with this style is that a <code>Thread</code> whose * <code>start</code> method has not been invoked and finished is never * destroyed, and so continues to occupy storage on the heap, even when * all user references to the <code>Thread</code> have vanished. * * The <i>RunnableDelegate</i> class allows this programming style to be * used without the overhead of unused <code>Thread</code> objects persisting * on the heap. The user class which previously subclassed <code>Thread</code> * now subclasses <i>RunnableDelegate</i>. <i>RunnableDelegate</i> implements * all methods of the <code>Thread</code> class, and so instances of * subclasses of <i>RunnableDelegate</i> can be used in the same way as * instances of subclasses of <code>Thread</code>. <i>RunnableDelegate</i> is * an abstract class, its concrete subclasses must implement the <code>run</code> * method of the <code>Runnable</code> interface. * */ abstract class RunnableDelegate implements Runnable { protected String name; protected Thread ourThread = null; protected Thread parentThread = Thread.currentThread(); protected ThreadGroup ourThreadGroup; protected int ourPriority = Thread.currentThread().getPriority(); protected boolean daemonFlag = false; public void setName(String nm) { name = nm; if (ourThread != null) { try { ourThread.setName(nm); } catch (NullPointerException e) {} } } public String getName() { return name; } public void setPriority(int p) throws IllegalArgumentException { if (p < Thread.MIN_PRIORITY || p > Thread.MAX_PRIORITY) { throw new IllegalArgumentException(); } ourPriority = p; if (ourThread != null) { try { ourThread.setPriority(p); } catch (NullPointerException e) {} } } public int getPriority() { if (ourThread == null) { return ourPriority; } try { return ourThread.getPriority(); } catch (NullPointerException e) {} return -1; } public void setDaemon(boolean flg) { daemonFlag = flg; if (ourThread != null) { try { ourThread.setDaemon(flg); } catch (NullPointerException e) {} } } public boolean isDaemon() { if (ourThread == null) { return daemonFlag; } try { return ourThread.isDaemon(); } catch (NullPointerException e) {} return false; } public final boolean isAlive() { if (ourThread == null) { return false; } try { return ourThread.isAlive(); } catch (NullPointerException e) {} return false; } public void interrupt() { if (ourThread != null) { try { ourThread.interrupt(); } catch (NullPointerException e) {} } } public boolean isInterrupted() { if (ourThread == null) { return false; } try { return ourThread.isInterrupted(); } catch (NullPointerException e) {} return false; } public final void join() throws InterruptedException { if (ourThread != null) { try { ourThread.join(); } catch (NullPointerException e) {} } } public final void join(long millis) throws InterruptedException { if (ourThread != null) { try { ourThread.join(millis); } catch (NullPointerException e) {} } } public void checkAccess() throws SecurityException { if (ourThread != null) { try { ourThread.checkAccess(); } catch (NullPointerException e) {} } if (parentThread != null) { try { parentThread.checkAccess(); } catch (NullPointerException e) {} } } public RunnableDelegate() { ourThreadGroup = Thread.currentThread().getThreadGroup(); } public RunnableDelegate(ThreadGroup grp) { if (grp == null) { ourThreadGroup = Thread.currentThread().getThreadGroup(); } ourThreadGroup = grp; } public RunnableDelegate(String nm) { this(); name = nm; } public RunnableDelegate(ThreadGroup grp, String nm) { this(grp); name = nm; } public void start() { ourThread = new Thread(ourThreadGroup, this, name); ourThread.setPriority(ourPriority); ourThread.setDaemon(daemonFlag); ourThread.start(); } public abstract void run(); // // -- Other methods to implement whatever Thread methods are needed -- // public void sleep(long millis) throws InterruptedException { Thread.sleep(millis); } public void yield() { Thread.yield(); } public void enumerate(Thread[] tarray) { ourThreadGroup.enumerate(tarray); } public int activeCount() { return ourThreadGroup.activeCount(); } }
11-06-2004