为什么这个Java进程无法终止?

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

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

不幸的是,即使有这个堆栈跟踪,我也看不到有任何明显的path可供进一步调查。

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

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

 $ /opt/jdk1.6.0_29/bin/jstack -l -F 5546
附加到进程ID 5546,请稍候...
debugging器成功连接。
检测到服务器编译
 JVM版本是20.4-b02
死锁检测:

没有发现死锁。

使用Printezis位查找对象大小并跳过...
线程5555 :(状态= BLOCKED)

locking拥有同步器:
     - 没有

线程5554 :(状态= BLOCKED)
  -  java.lang.Object.wait(long)@ bci = 0(解释帧)
  -  java.lang.ref.ReferenceQueue.remove(long)@ bci = 44,line = 118(Interpreted frame)
  -  java.lang.ref.ReferenceQueue.remove()@ bci = 2,line = 134(Interpreted frame)
  -  java.lang.ref.Finalizer $ FinalizerThread.run()@ bci = 3,line = 159(Interpreted frame)

locking拥有同步器:
     - 没有

线程5553 :(状态= BLOCKED)
  -  java.lang.Object.wait(long)@ bci = 0(解释帧)
  -  java.lang.Object.wait()@ bci = 2,line = 485(Interpreted frame)
  -  java.lang.ref.Reference $ ReferenceHandler.run()@ bci = 46,line = 116(Interpreted frame)

locking拥有同步器:
     - 没有

线程5548 :(状态= IN_VM)

locking拥有同步器:
     - 没有

(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,请稍候...
debugging器成功连接。
检测到服务器编译
 JVM版本是20.4-b02
遍历堆。 可能还要等一下...
使用Printezis位查找对象大小并跳过...
使用Printezis位查找对象大小并跳过...
对象直方图:

数字#实例#字节类描述
 -------------------------------------------------- ------------------------
 1:11356 1551744 * MethodKlass
 2:11356 1435944 * ConstMethodKlass
 3:914 973488 * ConstantPoolKlass
 4:6717 849032 char []
 5:16987 820072 * SymbolKlass
 6:2305 686048 byte []
 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 * System ObjArray
 13:1037 64584 int []
 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秒。

你总是可以用strace -f -p pid来查看Java进程正在做什么。 从它的外观(你不能得到没有-Fjstack ,并且线程5548显示没有调用堆栈并且是IN_VM),看起来线程5548正在花费太多时间来做某事,或者可能处于某种无限循环。

这可能是由于内存不足造成的。 我会尝试两件事情:

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

    -XX:+ HeapDumpOnOutOfMemoryError XX:HeapDumpPath = / tmp

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

我会怀疑一个内存问题。 您可能想要使用jstat来观察进程,并在需要杀死进程的时间周围使用jmap进行堆转储。 看看jstat是否表示连续的GC。 另外,您可能需要检查一般系统的运行状况(打开文件描述符,网络等)。 内存将是最简单的,所以我强烈建议从它开始。

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

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

当然,附上一个调试器 – GDB来检查进程到底在哪里。

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

我对jstack并不熟悉,但看起来它输出的信息更少,我更熟悉的线程转储。 尝试获取线程转储可能有用: kill -QUIT java_pid 。 请注意,转储到标准输出可能是控制台或记录文件,具体取决于您的设置。

如果很难确定stdout被引导到哪里,并且假定它正在转到一个文件,那么可以使用最近修改时间来查找候选文件。 这是建议在这篇博客的评论:

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

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

我遇到了类似的问题,我的JBOSS jvm得到了一个无限循环,最终它得到了OutOfMemory,我不能杀死这个进程而是kill -9。 我怀疑在大多数情况下的内存问题。

以下是一些工具,您可以使用这些工具本地化消耗CPU的进程部分:

  • perf / oprofile ,特别是opannotate – 非常适合查看地狱代码消耗的周期
  • stracegstack / gdb (正如别人所说)
  • systemtap功能非常强大,但是与基于ptrace的工具一样,如果你的问题不涉及系统调用,那么效率就会低很多。