【问题标题】:Why doesn't this thread pool get garbage collected?为什么这个线程池不被垃圾收集?
【发布时间】:2011-12-05 09:35:28
【问题描述】:

在这个代码示例中,ExecutorService 被使用一个并且允许超出范围。

public static void main(String[] args)
{
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    executorService.submit(new Runnable()
    {
        public void run()
        {
            System.out.println("hello");
        }
    });
}

一旦 executorService 超出范围,它应该被收集并最终确定。 ThreadPoolExecutor 中的 finalize() 方法调用了 shutdown()。

/**
 * Invokes {@code shutdown} when this executor is no longer
 * referenced and it has no threads.
 */
protected void finalize() {
    shutdown();
}

一旦调用了shutdown(),池线程应该终止并且JVM应该被允许退出。但是 executorSerivce 永远不会被收集,因此 JVM 仍然存在。甚至对 System.gc() 的调用似乎也不起作用。为什么即使在 main() 终止后 executorService 也没有被收集?

注意:我知道我应该自己调用 shutdown(),而且我总是在测试之外进行。我很好奇为什么最终确定在这里不能作为备份。

【问题讨论】:

  • 因为垃圾收集是“非确定性的”,即您无法预测它何时会发生,因此您无法准确预测 finalize 方法何时运行。您只能使 Objects 符合 GC 的条件,并在没有任何保证的情况下使用 System.gc() 建议 gc
  • 你永远不应该依赖终结来关闭或关闭某些东西。正如 jpse 已经说过的,垃圾收集是不确定的,并且不保证最终会运行。
  • JVM 没有退出,因为工作线程在 workQueue.take() 中被阻塞;

标签: java concurrency garbage-collection


【解决方案1】:

这实际上与 GC 的不确定性没有任何关系,尽管它没有帮助! (这是您示例中的一个原因,但即使我们“修复”它以消耗内存并强制收集,它仍然无法完成)

执行器创建的工作线程是内部类,它们具有对执行器本身的引用。 (他们需要它才能看到队列、运行状态等!)正在运行的线程不会被垃圾收集,因此池中的每个线程都具有该引用,它们将使执行程序保持活动状态,直到所有线程都死了。如果您不手动执行某些操作来停止线程,它们将永远运行并且您的 JVM 将永远不会关闭。

【讨论】:

  • 池中的Thread对池的引用在哪里?我认为可能是这种情况,并到处寻找并没有找到任何参考。
  • Worker是一个内部类,编译器会添加它
  • 您应该将有关 Worker 的信息添加到您的主要答案中,我认为这是我们都缺少的关键细节。
【解决方案2】:

Affe 是正确的;线程池的线程将防止它被垃圾收集。当您调用 Executors.newFixedThreadPool(3) 时,您会得到一个构造如下的 ThreadPoolExecutor:

ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

如果您阅读 ThreadPoolExecutor 的 JavaDoc,它会说:

一个不再在程序中引用并且没有剩余的池 线程将自动关闭。如果您想确保 即使用户忘记调用未引用的池也会被回收 shutdown(),然后你必须安排未使用的线程最终死掉, 通过设置适当的保持活动时间,使用零下限 核心线程和/或设置 allowCoreThreadTimeOut(boolean)。

如果您希望线程池按预期完成,您应该执行其中一项操作。

【讨论】:

  • 我还是不明白我需要做什么,如何设置keep-alive时间?
【解决方案3】:

因为垃圾收集是“非确定性的”,即您无法预测它何时会发生,因此您无法准确预测 finalize 方法何时运行。您只能使 Objects 符合 GC 的条件,并在没有任何保证的情况下使用 System.gc() 建议 gc。

更糟糕的线程是由 JVM 处理的特定于操作系统的线程,并且难以预测......

【讨论】:

  • 就这么简单;这是GC的定义:)
  • 你可以通过创建一堆垃圾来强制GC运行,它仍然没有被收集,所以看起来Affe的答案是正确的原因。
【解决方案4】:

终结者太难以预测了。依赖它们通常是不好的做法。 您可以在 Joshua Bloch 的 "Effective java" 中阅读更多相关信息(第 1.7 项)

【讨论】:

    【解决方案5】:

    一旦 executorService 超出范围,它应该被收集并最终确定。

    不是真的 - 一旦超出范围,它可以被收集并最终确定。 VM spec 中没有关于对象何时完成的保证,甚至 如果 它们已经完成:

    Java 编程语言没有指定多久会调用终结器,只是说它会在对象的存储被重用之前发生。

    【讨论】:

      【解决方案6】:

      如果您希望线程在 Executor 服务超出范围时完成,您应该避免,如 mjt 建议的那样,使用

          ExecutorService executorService = Executors.newFixedThreadPool(3);`
      

      并使用例如:

      ExecutorService executorService = new ThreadPoolExecutor(0, 3, 10, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
      

      【讨论】:

        猜你喜欢
        • 2010-12-19
        • 2014-08-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-10
        • 2013-04-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多