Java:为什么这个对象不被垃圾回收?

29

关于垃圾回收理论的一个问题。 我有以下方法。 它运行并退出该方法。即使在运行GC之后,为什么计时器仍然存在并继续“TICK”?我不相信在此方法退出后仍然有对计时器或timertask的引用,所以我期望计时器会被垃圾回收并引发异常。 请帮助我理解这个概念。

谢谢, 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);
    }

如果它真的消失了,那不是很可怕吗?这会让几乎所有的多任务处理变得更加困难。 - Bill K
4个回答

40

计时器对象实际上会在后台线程中安排任务的执行,因此后台线程将保留对计时器(和计时器任务)的引用,从而防止二者被垃圾回收。

以下是文档中适当的引用:

当所有对一个计时器对象的活动引用都消失且所有未完成的任务都已执行完毕后,计时器的任务执行线程会优雅地终止(并变为垃圾回收的对象)。然而,这可能需要任意长的时间。默认情况下,任务执行线程不作为守护线程运行,因此它能够阻止应用程序的终止。如果调用方希望快速终止计时器的任务执行线程,则应该调用计时器的取消方法。

因此,“所有未完成的任务都已执行完毕”的条件没有得到满足,线程永远不会终止,所以计时器/计时器任务永远不会被垃圾回收。


1
没错。根据 Timer 的 JavaDocs:“每个 Timer 对象对应一个单独的后台线程,用于按顺序执行所有计时器任务。” - Steve Kuo
然而,如果我取消计时器,计时器/计时器任务最终会被垃圾回收? - ptikobj

13
因为计时器拥有一个后台线程继续运行
对于每个计时器对象,都有一个单独的后台线程用于按顺序执行所有计时器任务。计时器任务应该快速完成。如果计时器任务需要过多时间才能完成,则会“独占”计时器的任务执行线程。这反过来又可能延迟后续任务的执行,当(如果)有问题的任务最终完成时,这些任务可能会“堆积”并快速执行。
由于它是后台线程,所以它会一直运行,直到JVM退出或停止。
更新:更多关于此的信息。"后台线程"与守护线程相同——通过类比与BSD守护进程命名。如果您查看Thread上的javadoc,您会发现:
将此线程标记为守护线程或用户线程。当仅运行所有守护线程时,Java虚拟机退出。
当主线程终止时,所有用户线程都会停止,只剩下守护线程。然后JVM关闭。对于一个好的时间 - 如果很短 - 从main中调用Thread.currentThread().setDaemon(true);
更新:嗯,我几乎做对了。你必须在构造时将计时器设置为守护进程。(这个改变了吗,还是我只是脑子短路了?)
无论如何,这里有示例代码:
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 ;
    }
}

2
强调Charlie的观点,活动线程是垃圾收集器的“根”对象。它们只有在死亡时才成为垃圾,因此无法被收集。从活动线程中强引用的任何内容也不是垃圾。加载的类以类似的方式工作。 - erickson
等等,这个计时器可以在调用它的程序结束后继续执行吗? - Benjamin Autin
@Charlie Martin:只有在没有其他非守护线程的情况下才是真的。例如,Swing应用程序通常具有仅调用GUI并死亡的main()。正如Rick Copeland的答案中解释的Javadoc引用所示,计时器确实可以防止JVM退出。 - Michael Myers
两个不同的程序可以在同一个JVM中运行吗?还是一个程序等于一个JVM? - Benjamin Autin
定义“不同的程序”。JVM启动一个主要例程。但是,没有任何限制,例如创建线程并从每个线程运行新类。这是一项有趣的练习;如果需要,您始终可以自己调用public static void main。 - Charlie Martin
显示剩余3条评论

2
计时器没有被垃圾回收,因为它仍在运行 - 其他某个对象(例如线程调度程序)仍具有对它的引用,这可能是在 scheduleAtFixedRate() 中创建的。

0

你怎么知道GC已经运行了?总的来说,垃圾回收并不是一个确定性的事情,也绝不会因为方法范围而触发。它与C++不同,离开函数作用域时析构函数会触发。当GC认为合适的时候,它会开始回收那些垃圾内存。


1
我已经通过NetBeans的调试模式显式地调用了GC。 - jbu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接