【问题标题】:Java: how to repair a hung thread?Java:如何修复挂起的线程?
【发布时间】:2014-10-23 00:41:26
【问题描述】:

请注意:我将其标记为 JClouds,因为如果您阅读了整个问题和随之而来的 cmets,我认为这要么是 JClouds 的错误,要么是对该库的滥用。

我有一个可执行的 JAR,它可以运行,工作一段时间,完成工作而不抛出任何错误/异常,然后在它应该退出时永远挂起。我使用 VisualVM 对其进行了分析(注意正在运行的线程),并且我还在应用程序挂起的位置(在 main() 方法的末尾)添加了一条日志语句以打印。这是我的主要方法的最后一部分:

Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
for(Thread t : threadSet) {
    String daemon = (t.isDaemon()? "Yes" : "No");
    System.out.println("The ${t.getName()} thread is currently running; is it a daemon? ${daemon}.");
}

当我的 JAR 执行此代码时,我看到以下输出:

The com.google.inject.internal.util.Finalizer thread is currently running; is it a daemon? Yes.
The Signal Dispatcher thread is currently running; is it a daemon? Yes.
The RMI Scheduler(0) thread is currently running; is it a daemon? Yes.
The Attach Listener thread is currently running; is it a daemon? Yes.
The user thread 3 thread is currently running; is it a daemon? No.
The Finalizer thread is currently running; is it a daemon? Yes.
The RMI TCP Accept-0 thread is currently running; is it a daemon? Yes.
The main thread is currently running; is it a daemon? No.
The RMI TCP Connection(1)-10.10.99.8 thread is currently running; is it a daemon? Yes.
The Reference Handler thread is currently running; is it a daemon? Yes.
The JMX server connection timeout 24 thread is currently running; is it a daemon? Yes.

我不认为我必须担心守护进程(如果我错了,请纠正我),因此将其过滤到非守护进程:

The user thread 3 thread is currently running; is it a daemon? No.
The main thread is currently running; is it a daemon? No.

显然,主线程仍在运行,因为某些东西阻止它退出。嗯,user thread 3 看起来很有趣。 VisualVM 告诉我们什么?

这是应用程序挂起时的线程视图(上面的控制台输出打印时发生的情况)。嗯user thread 3 看起来更可疑了!

所以在终止应用程序之前,我进行了线程转储。这是user thread 3 的堆栈跟踪:

"user thread 3" prio=6 tid=0x000000000dfd4000 nid=0x2360 waiting on condition [0x00000000114ff000]
    java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x0000000782cba410> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:744)

    Locked ownable synchronizers:
        - None

我以前从来没有分析过其中任何一个,所以这对我来说意味着胡言乱语(但对于受过训练的眼睛来说可能不是!)。

杀死应用程序后,VisualVM 的时间线停止每秒滴答/递增,我可以在时间线中水平向后滚动到 user thread 3 的创建位置,并开始它作为一个唠叨线程的生命:

但是我不知道如何判断代码user thread 3 的创建位置。所以我问:

  • 我如何知道创建 user thread 3 的内容以及创建位置(特别是因为我怀疑它是创建线程的第 3 方 OSS 库)?
  • 如何分类、诊断和修复此线程挂起?

更新:

这是我的代码,大约在同一时间user thread 3 似乎正在创建:

ExecutorService myExecutor = Executors.newCachedThreadPool();
for(Node node : nodes) {
    BootstrapAndKickTask bootAndKickTask = new BootstrapAndKickTask(node, ctx);
    myExecutor.execute(bootAndKickTask);
}

myExecutor.shutdown();
if(!myExecutor.awaitTermination(15, TimeUnit.MINUTES)) {
    TimeoutException toExc = new TimeoutException("Hung after the 15 minute timeout was reached.");
    log.error(toExc);

    throw toExc;
}

还有我的GitHub Gist,其中包含完整的线程转储。

【问题讨论】:

  • 这看起来像是 Executor 的 ThreadPool 没有被正确关闭。您可能需要在某些组件上调用shutdownterminateclose 方法。
  • 甚至守护线程也参与死锁。只需在此处提供所有堆栈跟踪。
  • @Basilevs 可以。如果 ExecutorService 仍然可以访问,比如从全局变量中,finalize() 将永远不会被调用,也永远不会关闭。默认情况下,为 ExecutorService 创建的线程被创建为非守护进程。
  • @Zac docs.oracle.com/javase/7/docs/api/java/lang/…。不过不建议使用。对于您发布的内容,您正在关闭它。这很奇怪。我的猜测是正在创建另一个池。
  • @Zac 如果是的话。然后我认为这是创建池的地方。在文件中搜索“用户线程 %d”。 github.com/jclouds/jclouds/blob/…

标签: java multithreading concurrency deadlock jclouds


【解决方案1】:

似乎正在发生的事情,但没有代码我无法确认,是您忘记在 ExecutorService 上调用 shutdown()/shutdownNow()。您正在离开,看起来是一个全局可访问的 ThreadPoolExecutor 对象,并且在您的主线程退出时仍在运行。由于它仍然是全局可访问的,因此 ExecutorService 永远不会调用它的 finalize 方法,也永远不会关闭它自己。默认情况下,为 ExecutorService 创建的线程被创建为非守护进程,并且会在需要它很长时间后继续运行。

您应该提供代码供我们查看,或者通过您的代码查看您使用 ThreadPoolExecutor 的位置,并在使用完毕后正确关闭它。

根据文档:

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

这意味着即使您的程序不再有对 ThreadPoolExecutor 的引用,只要池中至少有一个线程还活着,它就永远不会被回收。您可以查看docs 以了解解决此问题的方法。

【讨论】:

  • 文档说“程序中不再引用并且没有剩余线程的池将自动关闭。如果您想确保即使用户忘记调用关闭也可以回收未引用的池(),那么您必须通过设置适当的保持活动时间、使用零核心线程的下限和/或设置 allowCoreThreadTimeOut(boolean) 来安排未使用的线程最终死亡。”这意味着即使它无法访问并最终确定,默认情况下线程仍然不会被杀死。
  • 史密斯是正确的。除非调用shutdown,否则核心池中未使用的线程永远不会被销毁。该线程被锁定在调度程序队列上,并且不消耗任何 cpu。它根本没有“挂起”。它正在等待下一个任务。
  • @MichaelAnderson 没错。我的回答应该更清楚,如果池中存在一个正在运行的线程,它将永远不会最终确定。因为该线程包含对池的引用。
【解决方案2】:

如果您可以粘贴您使用的整个代码,那就太好了。 Apache jclouds 使用几个执行器来执行某些任务,您必须关闭它们。

确保在您从 jclouds ContextBuilder 获得的 contextapi 上调用 close() 方法。

【讨论】:

  • 感谢@Ignasi Barrera (+1) - 我现在正在测试,会尽快回复您。我没有关闭要么 API 或ChefContext,希望这样做!
  • 您只需要关闭您直接从 ContextBuilder 获取的内容。在这种情况下,关闭 ChefContext 就足够了。
【解决方案3】:

有两个错误:

  1. 您未能以异常安全的方式释放已分配的资源(线程池)
  2. 您发现了不知道如何处理的错误。

这是潜在的修复。 (我不确定我们是否应该在 finally 块中包含等待胎面完成)

ExecutorService myExecutor = Executors.newCachedThreadPool();
try {
    for(Node node : nodes) {
        BootstrapAndKickTask bootAndKickTask = new BootstrapAndKickTask(node, ctx);
        myExecutor.execute(bootAndKickTask);
    }
} finally {    
    myExecutor.shutdown();
    if(!myExecutor.awaitTermination(15, TimeUnit.MINUTES)) {
        TimeoutException toExc = new TimeoutException("Hung after the 15 minute timeout was reached.");
        log.error(toExc);
        throw toExc;
    }
}

【讨论】:

  • 感谢@Basilevs (+1) - 虽然这不是根本问题,但我确实添加了您的建议以使我的代码更加安全。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-22
  • 2013-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-05
相关资源
最近更新 更多