Java: GC and objects holding Threads.

devman

2[H]4U
Joined
Dec 3, 2005
Messages
2,400
So I came upon an interesting scenario working on my current project. Given that in Java a running thread cannot be garbage collected even if its reference count is zero. Can an object holding a running thread be garbage collected? Reason I'm interested is that if a thread holding object can be GC'd regardless of the thread status then I could override finalize() to stop the thread. Example code below.

Code:
public class MyClass {
	/**
	* anon thread implementation as class member.
	*/
    private Thread myThread = new Thread(){
		
		@Override
		public void start(){
			this.setDaemon(true);
			super.start();
		}
		
		@Override
		public void run(){
			/* Do something until interrupted */
		}
		
		@Override
		public void interrupt(){
			/* 
				...
				Do something to stop the thread
				...
			*/
			//Call super fucntion to set the Interrupted status of the thread.
			super.interrupt();
		}
	};
	
	//C'tor
	public MyClass(){
		myThread.start();
	}
	
	//MyClass methods
	public void doSomething(){
		//do something to MyClass Object
	}
	
	@Override
	protected void finalize(){
		//Stop the thread before the object gets GC'd
		myThread.interrupt();
		//Finally let the JVM GC the object.
		super.finalize();
	}
	
};

So any ideas if the JVM will even attempt to GC a MyClass object while myThread is running if said MyClass object had a zero reference count.

UPDATE: I think I finally figured it out, see post 13.
http://hardforum.com/showpost.php?p=1033239168&postcount=13
 
I'm no threading expert... (nor Java expert, for that matter) so this is just based on what I know about GC in Java...

In order for an Object to be GC'd, wouldn't there have to be no more references to it?
and if that's the case, how would the thread be running, if there's no longer an instance to the object that contains the thread?

Like I said, i'm no thread expert... but I suspect that if you have a thread running, and you kill it's container object, wouldn't it be interrupted?
 
This is from the book Java Threads:

The threading system keeps references to all threads that are running in the system. This is needed in order to support the currentThread() and enumerate() methods of the Thread class. The garbage collector will not be able to collect the thread object until the threading system also dereferences the object, which won't happen until the thread is no longer alive.

You need a thread to just not be running, at which point Java's thread manager will let it go and it's free for GC to work on it.

Haven't read this, not sure how helpful it'll be.
http://java.sun.com/developer/technicalArticles/javase/finalization/
 
I'm no threading expert... (nor Java expert, for that matter) so this is just based on what I know about GC in Java...

In order for an Object to be GC'd, wouldn't there have to be no more references to it?
and if that's the case, how would the thread be running, if there's no longer an instance to the object that contains the thread?

Like I said, i'm no thread expert... but I suspect that if you have a thread running, and you kill it's container object, wouldn't it be interrupted?

I went down that line of thinking as well, but there is a caveat to the reference counting rule with regards to Threading and that is that a currently running thread cannot be GC'd even if its reference count is zero. So the point in question here is, since the thread that the object is holding cannot be GC'd will the JVM even attempt to GC the object? If the JVM will attempt to GC the object then I can use the above code to terminate the thread so it doesn't get orphaned. If the JVM won't attempt to GC the object while it holds a reference to a running Thread then I'll have to be more creative about it.
 
This is from the book Java Threads:



You need a thread to just not be running, at which point Java's thread manager will let it go and it's free for GC to work on it.

Haven't read this, not sure how helpful it'll be.
http://java.sun.com/developer/technicalArticles/javase/finalization/

Ok, so a running thread can never have a reference count of zero because the JVM retains a reference. So the fundamental question here is, if I retain a reference to a member of an object will the JVM attempt to GC the parent object when it's own reference count reaches zero? If so then I can indeed override finalize to end the thread.

I'll probably have to experiment with this a little when I get time.
 
I went down that line of thinking as well, but there is a caveat to the reference counting rule with regards to Threading and that is that a currently running thread cannot be GC'd even if its reference count is zero. So the point in question here is, since the thread that the object is holding cannot be GC'd will the JVM even attempt to GC the object? If the JVM will attempt to GC the object then I can use the above code to terminate the thread so it doesn't get orphaned. If the JVM won't attempt to GC the object while it holds a reference to a running Thread then I'll have to be more creative about it.

i dont think the GC cares how many objects that instance is pointing to, once it's reference count goes to 0 it fair game for the GC. i think you might be thinking of GC as some deep-cleanup routine that cleans up hanging objects as well (and therefore all hanging object would need to be pointed to by only that object), but i dont believe that's the case. if the objects hanging of the GC'd object are only pointed to by members of the GC'd object,t heir ref counts go to 0 and they're free game as well. otherwise they'd stick around

so i think, yes, in theory you could use finalize() to stop a thread (i dont know if this is reliable or not). because the number of references to that thread object doesnt really come into play when determining whether or not to GC the containing object.
 
Depending on finalize() won't work since whether or not finalize() is called is nondeterministic, and if it is called, it's only called when the GC operates on the object. And we're not even sure if the GC will operate on it yet.

I do not know enough of the inner workings of a JVM to answer this question with certainty, but I think in this case, the object possessing the thread is not tagged for garbage collection until the thread it possesses is done, since the JVM thread manager will have a reference to it (or its thread).
 
i dont think the GC cares how many objects that instance is pointing to, once it's reference count goes to 0 it fair game for the GC. i think you might be thinking of GC as some deep-cleanup routine that cleans up hanging objects as well (and therefore all hanging object would need to be pointed to by only that object), but i dont believe that's the case. if the objects hanging of the GC'd object are only pointed to by members of the GC'd object,t heir ref counts go to 0 and they're free game as well. otherwise they'd stick around

so i think, yes, in theory you could use finalize() to stop a thread (i dont know if this is reliable or not). because the number of references to that thread object doesnt really come into play when determining whether or not to GC the containing object.

I was incorrect about "why" running threads can't be GC'd, but not about the bottom line which is running threads cannot be GC'd.

Depending on finalize() won't work since whether or not finalize() is called is nondeterministic, and if it is called, it's only called when the GC operates on the object. And we're not even sure if the GC will operate on it yet.

Yes and no. When finalization occurs is non deterministic, however finalize is always invoked when an object would be gc'd. After finalize returns the JVM again determines if the object can be reached. If it can't the object is discarded. The question I have here is whether or not a running thread contained in an object would prevent said object from being garbage collected (which would invoke finalize on that object).

here's a nice little link to issues with overriding finalize()
(the thread author is a personal friend of mine... and we're discussing your issue right now :) )
http://stackoverflow.com/questions/158174/why-would-you-ever-implement-finalize

Very cool. Thanks.
 
This is an interesting question and I talked to my friend about this and he doesn't know the answer either, so for now we're all just making assumptions.

I think experimentation is in order, and it might even differ based on which JVM you're using. I might have to read a JVM implementation to see how it's handled.

If you get an answer, let us know.
 
So I did some experimenting with this concept and the results were rather interesting.

First is the situation I described in my OP.
Code:
public class ThreadHolder {

	 private Thread myThread = new Thread(){
			
		 	Boolean halt = false;
		 
			@Override
			public void start(){
				this.setDaemon(true);
				super.start();
			}
			
			@Override
			public void run(){
				
					synchronized(halt){
						try {
							halt.wait(5000);
						} catch (InterruptedException e) {
							System.out.println("Contained Thread was interrupted.");
						}
					}
				System.out.println("Contained Thread ran to completion.");
			}
			
			@Override
			public void interrupt(){
				halt = true;
				//Call super function to set the Interrupted status of the thread.
				super.interrupt();
			}
		};
	
	private Object myMemberObject = new Object();
		
	
	public ThreadHolder(){
		myThread.start();
		
	}
	
	//Unused in this example.
	public Object getReferenceToMemberObject(){
		return myMemberObject;
	}
	
	@Override
	protected void finalize(){
		System.out.println("Thread Holder Finalized");
		myThread.interrupt();
	}
	
	public static void main(String[] args) {
		try{
			new ThreadHolder();
			System.out.println("Garbage Collect #1.");
			System.gc();
			System.out.println("Main thread sleep.");
			Thread.sleep(10000);
			System.out.println("Main thread wakeup.");
			System.out.println("Garbage Collect #2.");
			System.gc();
			Thread.sleep(100);
		}catch(Exception e){
			//Nothing going on here.
		}
		
		System.out.println("End Program.");
	}
}

This produces following output.

Code:
Garbage Collect #1.
Main thread sleep.
Contained Thread ran to completion.
Main thread wakeup.
Garbage Collect #2.
Thread Holder Finalized
End Program.

The main thread does not maintain a reference to the ThreadHolder object.
Note that the JVM does not attempt to GC the ThreadHolder until after the contained thread dies.

Commenting out myThread.start() in the ThreadHolder constructor yields this.

Code:
Garbage Collect #1.
Main thread sleep.
Thread Holder Finalized
Main thread wakeup.
Garbage Collect #2.
End Program.

ThreadHolder is GC'd almost immedietly.

Ok new code. Generalizing the above scenario to any object.

Code:
public class ObjectHolder {

	private Object myMemberObject;	
	
	public ObjectHolder(){
		myMemberObject = new Object();
		System.out.println("Member Object: "+ myMemberObject);
	}

	public Object getReferenceToMemberObject(){
		return myMemberObject;
	}
	
	@Override
	protected void finalize(){
		System.out.println("Object Holder Finalized");
	}
	
	public static void main(String[] args) {
		Object RefHolder = new ObjectHolder().getReferenceToMemberObject();
		try{
			System.out.println("Same Object: " + RefHolder);
			System.out.println("Garbage Collect #1.");
			System.gc();
			System.out.println("Main thread sleep.");
			Thread.sleep(10000);
			System.out.println("Main thread wakeup.");
			System.out.println("Garbage Collect #2.");
			System.gc();
			Thread.sleep(100);
		}catch(Exception e){
			//Nothing going on here.
		}
		
		System.out.println("End Program.");
	}
}

Produces this output.

Code:
Member Object: java.lang.Object@3e25a5
Same Object: java.lang.Object@3e25a5
Garbage Collect #1.
Main thread sleep.
Object Holder Finalized
Main thread wakeup.
Garbage Collect #2.
End Program.

Very interesting, because in this case even though a reference was maintained to a contained object the JVM GC'd the ObjectHolder object anyway, not the same behavior as above.

Let me try that with a Thread.

Code:
public class ThreadHolder {

	 private Thread myThread = new Thread(){
			
		 	Boolean halt = false;
		 
			@Override
			public void start(){
				this.setDaemon(true);
				super.start();
			}
			
			@Override
			public void run(){
				
					synchronized(halt){
						try {
							halt.wait(5000);
						} catch (InterruptedException e) {
							System.out.println("Contained Thread was interrupted.");
						}
					}
				System.out.println("Contained Thread ran to completion.");
			}
			
			@Override
			public void interrupt(){
				halt = true;
				//Call super function to set the Interrupted status of the thread.
				super.interrupt();
			}
		};
	
	
		
	
	public ThreadHolder(){
		
		myThread.start();
		System.out.println("Member Object: "+ myThread);
	}
	
	public Object getReferenceToMemberObject(){
		return myThread;
	}
	
	@Override
	protected void finalize(){
		System.out.println("Thread Holder Finalized");
		myThread.interrupt();
	}
	
	public static void main(String[] args) {
		Object Holder = new ThreadHolder().getReferenceToMemberObject();
		try{
			System.out.println("Same Object: " + Holder);
			System.out.println("Garbage Collect #1.");
			System.gc();
			System.out.println("Main thread sleep.");
			Thread.sleep(10000);
			System.out.println("Main thread wakeup.");
			System.out.println("Garbage Collect #2.");
			System.gc();
			Thread.sleep(100);
		}catch(Exception e){
			//Nothing going on here.
		}
		System.out.println("End Program.");
	}
}

Output:

Code:
Member Object: Thread[Thread-0,5,main]
Same Object: Thread[Thread-0,5,main]
Garbage Collect #1.
Main thread sleep.
Contained Thread ran to completion.
Main thread wakeup.
Garbage Collect #2.
End Program.

Interesting, the ThreadHolder object didn't get GC'd even after the Thread died because main held a reference to it's member thread. This is contrary to what we saw just using a plain object.

Lets try with method scoping just to be sure. Modified excerpt below.

Code:
        public static void main(String[] args) {
		Object Holder;
		Holder = subMain();
		System.out.println("GC after leaving subMain");
		System.gc();
		System.out.println("End Program.");
	}

	private static Object subMain() {
		Object SubMainHolder = new ThreadHolder().getReferenceToMemberObject();
		try{
			System.out.println("Same Object: " + SubMainHolder);
			System.out.println("Garbage Collect #1.");
			System.gc();
			System.out.println("Main thread sleep.");
			Thread.sleep(10000);
			System.out.println("Main thread wakeup.");
			System.out.println("Garbage Collect #2.");
			System.gc();
			Thread.sleep(100);
		}catch(Exception e){
			//Nothing going on here.
		}
		return SubMainHolder;
	}

Produces the same output as the previous one.

Code:
Garbage Collect #1.
Main thread sleep.
Contained Thread ran to completion.
Main thread wakeup.
Garbage Collect #2.
GC after leaving subMain
End Program.

So basically my conclusion is that the JVM will not attempt to GC an object that references a thread that cannot be GC'd as a special case.

So the result is I cannot do as I suggested in my OP because the JVM will not attempt to finalize the object until it's member thread is able to be GC'd (dead and no more references). I will either need to maintain a reference to the thread after it's parent object goes out of scope or I will have to Thread.enumerate() to find the orphaned thread and interrupt it, otherwise the orphaned thread will continue to run and it's parent object (and presumably other objects contained by the parent object, although I didn't test that) will remain in memory, even though there are no references to said parent object.

@BillLeeLee:
I agree, this most certainly could be JVM dependent.
 
Looks like I'm a bit late to the thread, and I'm not sure I get what you ultimately want to do, but it sounds like you want to interrupt a thread when an object is GC-ed (or at least unreachable). If so, here's a different way to approach it. First, set up a class to take care of monitoring the object and interrupting the thread:

Code:
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

public class ThreadNotifier implements Runnable {

    private final ReferenceQueue refQueue;
    private final PhantomReference subjectRef;
    private final WeakReference targetRef;

    public ThreadNotifier(Object subject, Thread target) {
        this.refQueue = new ReferenceQueue();
        this.subjectRef = new PhantomReference(subject, this.refQueue);
        this.targetRef = new WeakReference(target);
    }

    public void run() {
        Reference ref = null;
        do {
            try {
                ref = refQueue.remove(); // blocking call
            } catch (InterruptedException ignore) {}
        } while (ref == null);
        Thread target = (Thread)this.targetRef.get();
        if (target != null) {
            target.interrupt();
            this.targetRef.clear();
        }
    }
}

Then a test class:

Code:
public class Main {

    public static void main(String[] args) {
        // object to be monitored
        Object subject = new Object();
        // thread to be interrupted when object no longer reachable
        Thread target = new Thread(new Runnable() {
            public void run() {
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println("Target thread working");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException exc) {
                        System.out.println("Target thread caught InterruptedException");
                        break;
                    }
                }
                System.out.println("Target thread exiting");
            }
        }, "Target");
        target.setDaemon(true);
        target.start();

        Thread notifier = new Thread(new ThreadNotifier(subject, target), "Notifier");
        notifier.setDaemon(true);
        notifier.start();
        
        // let main thread exit, but start a non-daemon thread to keep JVM from exiting
        new Thread(new Runnable() {
            public void run() {
                while (true) {
                    System.out.println("GC runner working");
                    for (int i = 0; i < 10; i++) {
                        System.gc();
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException ignore) {}
                    }
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException ignore) {}
                }
            }
        }, "GC Runner").start();
        
        // before exiting main thread (strong refs to subject and target go out of scope), sleep for a bit
        try {
            Thread.sleep(10000);
        } catch (InterruptedException ignore) {}
    }
}

And here's some sample output:

Code:
Target thread working
GC runner working
Target thread working
Target thread working
Target thread working
GC runner working
Target thread working
Target thread working
Target thread working
Target thread working
GC runner working
Target thread working
Target thread working
Target thread working
GC runner working
Target thread caught InterruptedException
Target thread exiting
GC runner working
GC runner working
GC runner working
GC runner working
...

Since the "GC Runner" thread is a non-daemon thread that never exits, you have to issue a ^C to stop the program.
 
Never being satisfied with my own answers I did some more research on this.

It turns out the object being a thread had nothing to do with the object being GC'd or not. My original hypothesis was actually correct. What led me astray was the use of an anonymous class implementation of Thread as a field of the ThreadHolder class. An object of a class which makes use of anon classes cannot be GC'd so long as any objects of those anon classes cannot be GC'd. As for exactly why that is I didn't get that far, I'm going to make an educated guess though that a classloader must be maintaining a reference to that object as long as objects using it's anon classes are in memory and not ready to be GC'd. I'm sure there is a far more complicated explanation for it but that is well beyond the scope of this post.

What this means is, in my thread holder example, because I had an anon class object (the thread impl) that could not be GC'd (running threads can't be GC'd) the parent object could not be GC'd. If make an actual implementation of class extending thread and set up the same scenario, the ThreadHolder object is GC'd as soon as it's reference count hits zero, the thread it was holding will be interrupted because I overrode the finalize() method of ThreadHolder. If the ThreadHolder.finalize() method did not interrupt the thread it would continue running until it completed execution (or the JVM shut down if it is a daemon thread), at which point it would also be GC'd.

Code:
public class ThreadHolder {

    private MyThreadImpl myThread = new MyThreadImpl();
	
	
		
	public ThreadHolder(){
		myThread.start();
		System.out.println("Member Object: "+ myThread);
	}
	
	public Object getReferenceToMemberObject(){
		return myThread;
	}
	
	@Override
	protected void finalize() throws Throwable{
		System.out.println("Thread Holder Finalized");
		myThread.interrupt();
		super.finalize();
	}
	
	public static void main(String[] args) {
		try{
			subMain();
			System.out.println("Garbage Collect #3.");
			System.gc();
			Thread.sleep(100);
			System.out.println("End Program.");
			
		}catch(Exception e){
			//Nothing going on here.
		}
	}

	private static void subMain() {
		Object Holder = new ThreadHolder().getReferenceToMemberObject();
		try{
			System.out.println("Same Object: " + Holder);
			System.out.println("Garbage Collect #1.");
			System.gc();
			System.out.println("Main thread sleep.");
			Thread.sleep(10000);
			System.out.println("Main thread wakeup.");
			System.out.println("Garbage Collect #2.");
			System.gc();
			Thread.sleep(100);
		}catch(Exception e){
			//Nothing going on here.
		}
	}
}

class MyThreadImpl extends Thread{
	Boolean halt = false;
	 
	@Override
	public void start(){
		this.setDaemon(true);
		super.start();
	}
	
	@Override
	public void run(){
		
			synchronized(halt){
				try {
					halt.wait(5000);
				} catch (InterruptedException e) {
					System.out.println("Contained Thread was interrupted.");
				}
			}
		System.out.println("Contained Thread ran to completion.");
	}
	
	@Override
	public void interrupt(){
		halt = true;
		//Call super function to set the Interrupted status of the thread.
		super.interrupt();
	}
}

So conclusion is:
Yes I can override the finalize() method to shutdown threads held by a parent object when the parent object would be GC'd.

Also learned:
Objects of classes which have anon classes in them cannot be GC'd as long as references are maintained to objects of that contained anon class created by that particular object.

Whew! :p
 
Back
Top