【问题标题】:Why is this Java process failing to terminate?为什么这个 Java 进程无法终止?
【发布时间】:2011-11-08 04:40:28
【问题描述】:

我在构建服务器上遇到一个间歇性问题,其中构建中的 Java 进程以某种方式无法终止,并且似乎永远继续运行(使用 100% 的 CPU)(我已经看到它运行了 2 天以上)周末通常需要大约 10 分钟)。 kill -9 pid 似乎是停止进程的唯一方法。

我尝试在进程上调用kill -QUIT pid,但它似乎没有产生任何到 STDOUT 的堆栈跟踪(也许它没有响应信号?)。没有 -F force 选项的 jstack 似乎无法连接到正在运行的 JVM,但使用 force 选项它确实会产生下面的输出。

不幸的是,即使有该堆栈跟踪,我也看不到任何明显的进一步调查路径。

据我所知,它显示了两个运行 Object.wait 的“BLOCKED”线程(它们的堆栈似乎只包含核心 Java 代码,没有我们的代码)和第三个是“IN_VM”,没有堆栈输出。

我应该采取哪些步骤来收集有关问题原因的更多信息(或者更好的是,我该如何解决它)?

$ /opt/jdk1.6.0_29/bin/jstack -l -F 5546 正在附加到进程 ID 5546,请稍候... 调试器连接成功。 检测到服务器编译器。 JVM版本为20.4-b02 死锁检测: 没有发现死锁。 使用 Printezis 位查找对象大小并跳过... 线程 5555:(状态 = 阻塞) 锁定的可拥有同步器: - 没有任何 线程 5554:(状态 = 阻塞) - java.lang.Object.wait(long) @bci=0 (解释帧) - java.lang.ref.ReferenceQueue.remove(long) @bci=44, line=118 (解释帧) - java.lang.ref.ReferenceQueue.remove() @bci=2, line=134(解释帧) - java.lang.ref.Finalizer$FinalizerThread.run() @bci=3, line=159(解释帧) 锁定的可拥有同步器: - 没有任何 线程 5553:(状态 = 阻塞) - java.lang.Object.wait(long) @bci=0 (解释帧) - java.lang.Object.wait() @bci=2, line=485(解释帧) - java.lang.ref.Reference$ReferenceHandler.run() @bci=46, line=116(解释帧) 锁定的可拥有同步器: - 没有任何 线程 5548:(状态 = IN_VM) 锁定的可拥有同步器: - 没有任何

(Java 版本 1.6.0 更新 29,在 Scientific Linux 版本 6.0 上运行)

更新:

运行strace -f -p 894 会产生看似无穷无尽的...

[pid   900] sched_yield()               = 0
[pid   900] sched_yield()               = 0
...

然后当Ctrl-Cd时

Process 894 detached
...
Process 900 detached
...
Process 909 detached

jmap -histo 894 未连接但jmap -F -histo 894 返回...

正在附加到进程 ID 894,请稍候... 调试器连接成功。 检测到服务器编译器。 JVM版本为20.4-b02 在堆上迭代。可能还要等一下... 使用 Printezis 位查找对象大小并跳过... 使用 Printezis 位查找对象大小并跳过... 对象直方图: num #instances #bytes 类描述 -------------------------------------------------- ---------------------- 1:11356 1551744 * MethodKlass 2:11356 1435944 * ConstMethodKlass 3:914 973488 * 常量池类 4:6717 849032 字符[] 5:16987 820072 * SymbolKlass 6:2305 686048字节[] 7:914 672792 * InstanceKlassKlass 8:857 650312 * ConstantPoolCacheKlass 9:5243 167776 java.lang.String 10: 1046 108784 java.lang.Class 11: 1400 87576 短[] 12: 1556 84040 * 系统对象数组 13: 1037 64584 整数 [] 14:103 60152 * ObjArrayKlassKlass 15:622 54736 java.lang.reflect.Method 16: 1102 49760 java.lang.Object[] 17:937 37480 java.util.TreeMap$Entry 18: 332 27960 java.util.HashMap$Entry[] 19:579 27792 java.nio.HeapByteBuffer 20: 578 27744 java.nio.HeapCharBuffer 21: 1021 24504 java.lang.StringBuilder 22: 1158 24176 java.lang.Class[] 23: 721 23072 java.util.HashMap$Entry 24:434 20832 java.util.TreeMap 25:689 18936 java.lang.String[] 26: 238 17440 java.lang.reflect.Method[] 27:29 16800 * MethodDataKlass 28: 204 14688 java.lang.reflect.Field 29: 330 13200 java.util.LinkedHashMap$Entry 30: 264 12672 java.util.HashMap ... 585:1 16 java.util.LinkedHashSet 586:1 16 sun.rmi.runtime.NewThreadAction$2 587:1 16 java.util.Hashtable$EmptyIterator 588:1 16 java.util.Collections$EmptySet 合计:79700 8894800 堆遍历耗时 1.288 秒。

【问题讨论】:

  • 您可以尝试在运行服务器的控制台上按 [CTRL]+[SYSREQ]。这将为您提供一个尽可能详细的线程转储。

标签: java linux debugging


【解决方案1】:

您可以随时发送strace -f -p pid 来查看Java 进程在做什么。从它的外观来看(如果没有-F,您将无法获得jstack,并且线程5548 没有显示调用堆栈并且是IN_VM),看起来线程5548 做某事需要太多时间,或者可能处于某个无限循环中.

【讨论】:

  • 正在运行 strace -f -p 894 我得到一堆行说 [pid 900] sched_yield() = 0...有趣...
  • @MattSheppard:从这一点开始,我会遵循 bestsss 的建议并获得一些回溯。如果您的系统有pstack,只需执行pstack <pid>。否则你将不得不做gdb /path/to/java <pid>和来自gdb的btquit
  • 下次出现的时候我会试试的。
【解决方案2】:

这也可能是由内存不足引起的。我会尝试两件事:

  • 通过添加JVM参数在OutOfMemory上启用自动堆转储

    -XX:+HeapDumpOnOutOfMemoryError XX:HeapDumpPath=/tmp

  • 尝试使用 JConsole 连接到您的 JVM,看看是否有任何异常模式

【讨论】:

  • 不幸的是,jconsole 似乎没有连接到它(如果我远程尝试就会超时,如果我从服务器运行它,相关的 PID 在列表中显示为灰色)。我会看看如何将这些参数传递给正确的 JVM 调用。
【解决方案3】:

我怀疑是内存问题。您可能希望使用 jstat 查看进程,并在需要终止进程时使用 jmap 进行堆转储。看jstat是否表示连续GC。此外,您可能希望检查系统的总体健康状况(打开的文件描述符、网络等)。记忆是最简单的,所以我强烈建议从它开始。

【讨论】:

  • 不幸的是 jstat 给了我消息Could not synchronize with target。 jmap 似乎确实能够产生一些信息(我会更新上面的问题)。
  • 除了上面的直方图之外,我似乎确实能够使用jmap -F -dump:format=b,file=heap.bin 894 获得堆 dup,但我不太确定如何处理它。
  • 您可以使用 eclipse MAT 查看堆转储。寻找泄漏嫌疑人。但是,奇怪的是您无法使用 jstat。你在用什么命令?
【解决方案4】:

在进程正常运行时通过 jstack -F 拍摄快照(-F 必须存在,它产生的快照与 jstack 不同)。线程号不是 Thread.id 而是系统一。 5548 似乎是在 Finalizer 和 RefCounter 之前创建的(它们不是问题的根源),因此它应该是 GC 线程或某个编译器线程。

100% 可能意味着监视器中有一些错误。 Java(热点)监视器使用非常简单的自旋锁定机制来确保所有权。

当然,还要附加一个调试器 - GDB 来检查进程究竟卡在哪里。

【讨论】:

    【解决方案5】:

    线程 5554 可能表明您有很多带有 finalize 方法的对象,和/或 finalize 方法有一些问题。可能值得一看。

    我不熟悉 jstack,但看起来它输出的信息比我更熟悉的线程转储少。尝试获取线程转储可能有用:kill -QUIT java_pid。请注意,根据您的设置,转储会转到可能是控制台或日志文件的标准输出。

    如果很难确定 stdout 被定向到哪里,并且假设它是一个文件,您可以使用 find by 最近的修改时间来识别候选文件。这是在this blog post的评论中建议的:

    你可以在你的根目录下运行 find[2] 命令并找出什么 在最后 x 秒内更改。我通常用 find 来帮助我 访问过去 10 分钟内更改的所有日志,例如:find /var/tomcat -mmin -3 -print (打印出所有修改过的文件 /var/tomcat 最后 3 分钟)。

    请注意,如果您使用 -Xrs 运行 JVM,这意味着将不会安装 SIGQUIT 信号处理程序,并且您将无法使用该方法请求线程转储。

    【讨论】:

    • 5554 是终结器,除非有需要终结的东西,否则应将其停放。它不应该阻止进程终止。 kill -QUIT 是一个非常好的主意,因为它可以帮助您弄清楚 5555 发生了什么,这看起来更有可能是罪魁祸首。
    • 下次出现问题时我会尝试kill -QUIT,看看是否能提供更多信息,谢谢。我认为可能有一些自定义终结器,但他们应该做的就是关闭打开的文件。我想这是快速且相当安全的,但也许不是......
    • 我认为@philwb 是对的。终结器可能没有问题 - 终结器线程正在等待某事做,而不是在工作时被阻塞。
    • kill -QUIT 不幸的是似乎没有任何效果(即没有输出到进程 STDOUT 所在的文件)。也许它没有响应信号(kill -9 似乎是唯一能阻止它的东西)。
    • 您是该进程的所有者吗?
    【解决方案6】:

    我遇到了类似的问题,我的 JBOSS jvm 出现了一个无限循环,最终它得到了 OutOfMemory,我无法杀死进程,但 Kill -9。在大多数情况下,我怀疑内存问题。

    【讨论】:

      【解决方案7】:

      这里有一些工具,您可以使用它们来本地化进程中消耗 CPU 的部分:

      • perf / oprofile,尤其是 opannotate -- 非常适合查看到底是什么代码正在消耗周期
      • strace, gstack / gdb(其他人提到过)
      • systemtap 非常强大,但在某些方面与基于 ptrace 的工具相同(如果您的问题不涉及系统调用,那么它的效率会低得多)。

      【讨论】:

        猜你喜欢
        • 2017-07-03
        • 1970-01-01
        • 2014-01-08
        • 2015-01-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多