views:

664

answers:

4

Hi all,

Quick question about the theory of GCing. I have the following method. It runs, and exits the method. How come even after GC is run, the timer still exists and keeps "TICK"ing? I don't believe there's still a reference to timer or the timertask anymore after this method exists, so I'd expect the timer to be GCed and cause an exception. Please help me understand this concept.

Thanks, jbu

private void startTimer()
    {
        Timer timer= new Timer();
        TimerTask timerTask= new TimerTask()
        {

            @Override
            public void run()
            {
                System.out.println("TICK");
            }
        };

        timer.scheduleAtFixedRate(timerTask,
                0,
                500);
    }
+1  A: 

How do you know the GC ran? Garbage collection in general is not a deterministic thing, and it's definitely not triggered by method scope. It's not like C++ where you leave scope of a function and destructors fire. It will get around to collecting that memory if and when the GC feels like it.

Nick Veys
I have called the GC explicitly through the debugging mode of netbeans.
jbu
+18  A: 

The Timer object actually schedules tasks to be executed in a background thread, so that background thread maintains a reference to the Timer (and the TimerTask), which prevents both from being garbage-collected.

Here is the appropriate quote from the docs:

After the last live reference to a Timer object goes away and all outstanding tasks have completed execution, the timer's task execution thread terminates gracefully (and becomes subject to garbage collection). However, this can take arbitrarily long to occur. By default, the task execution thread does not run as a daemon thread, so it is capable of keeping an application from terminating. If a caller wants to terminate a timer's task execution thread rapidly, the caller should invoke the the timer's cancel method.

So the condition that "all outstanding tasks have completed execution" is not satisfied, and the thread never terminates, so the Timer/TimerTask is never GC'd.

Rick Copeland
Exactly. From Timer's JavaDocs: "Corresponding to each Timer object is a single background thread that is used to execute all of the timer's tasks, sequentially."
Steve Kuo
+2  A: 

The timer's not being garbage collected because it's still running -- some other object (such as the thread scheduler) still has a reference to it, which was probably created inside scheduleAtFixedRate().

Adam Rosenfield
+9  A: 

Because a Timer has a background thread that continues running:

Corresponding to each Timer object is a single background thread that is used to execute all of the timer's tasks, sequentially. Timer tasks should complete quickly. If a timer task takes excessive time to complete, it "hogs" the timer's task execution thread. This can, in turn, delay the execution of subsequent tasks, which may "bunch up" and execute in rapid succession when (and if) the offending task finally completes.

Since it's a background thread, it continues until the JVM exits or it's stopped.

Update: a little more on this. A "background thread" is the same thing as a daemon thread -- named by analogy with a BSD daemon process. If you see the javadocs on Thread, you'll find:

Marks this thread as either a daemon thread or a user thread. The Java Virtual Machine exits when the only threads running are all daemon threads.

When your main terminates, all the user threads stop, leaving only daemon threads. The JVM then shuts down. For a good time — if short — call Thread.currentThread().setDaemon(true); from main.

Update: Ack. I had that almost right. You have to make the timer a daemon at construction time. (Did this change, or did I just have a brain failure?)

Anyway, here's example code:

import java.util.*;

class Chatter extends TimerTask {
    public void run(){
        System.err.println("Timer run.");
    }
}

public class TryThread {
    public static void main(String[] argv){
        // If argument is true, only runs a few times.
        Timer t = new Timer(false);
        t.schedule(new Chatter(), 1L, 1L);
        return ;
    }
}
Charlie Martin
Just to emphasize Charlie's point, live Threads are "root" objects for the garbage collector. They aren't garbage until they die, and can't be collected. Anything strongly reference-able from a live thread is not garbage either. Loaded classes work in a similar way.
erickson
Wait, so this timer can continue executing even after the program that invoked it finishes?
toast
No, because when the public static void main that was invoked at the start terminates, the JVM overall shuts down, which means stopping and killing background threads. It doesn't really get GC'd though because when the process dies, all the memory is released.
Charlie Martin
@Charlie Martin: That's only true if there are no other non-daemon threads. Swing apps, for example, routinely have a main() that merely invokes the GUI and dies. Timers can indeed keep the JVM from exiting, as explained by the Javadoc quote in Rick Copeland's answer.
Michael Myers
Can two different programs run in the same JVM? Or is it one program = one JVM?
toast
Define "different programs". The JVM starts one main routine. But nothing keeps you from, say, creating threads and running a new class from each one. It makes an amusing exercise; you *can* always call public static void main yourself if you want.
Charlie Martin
How would it be considered if a web browser started two separate applets from different sites? Would that start two JVMs or would they share the same JVM?
toast
That's free to the implementation, but most browsers run one JVM. Google Chrome doesn't.
Charlie Martin