Java SE 6 故障排除指南 – 5、挂起或循环进程故障排除

标签: JVM java 故障排除 | 发表时间:2014-05-15 22:22 | 作者:coderbee
出处:http://coderbee.net

本章为挂起或循环进程的故障排除在特定程序上提供了信息和指导。

问题在涉及挂起或循环进程时发生。挂起可能因为多种原因发生,但经常是源于程序代码、API代码或库代码里的死锁。挂起甚至是因为 HotSpot VM的bug。

有时候,一个表面上是挂起的可能是个循环。例如,VM进程里的bug导致一个或多个线程进入死循环,会消耗掉所有可得CPU周期。

诊断挂起的最初步骤是找出VM进程是空闲还是消耗了所有可得CPU周期,为做这个要求使用操作系统工具。如果进程表现为繁忙且消耗了所有可得CPU周期,那么问题很可能是循环线程而不是死锁。

5.1 诊断循环进程

如果VM进程表现为循环,第一步是尝试获取线程转储。如果获取了线程转储,通常哪个线程在循环是很明显的。如果循环线程被认定,线程转储里的栈轨迹可以提供线程在哪里(可能还有为什么)循环的方向。

如果程序控制台(标准输入输出)可得,按下 Ctrl-\ 键组合(Solaris OS 或 Linux上)或 Ctrl-Break 键组合(Windows上)引起 HotSpot VM 打印线程转储,包括线程状态。在Solaris OS 和Linux上,线程转储还可以通过发送 SIGQUIT 到进程(命令: kill -QUIT <pid>)来获得。在这种情况下,线程转储将被打印到目标进程的标准输出。输出也可以重定向到文件,取决于进程如果启动。

如果Java进程是带 -XX:+PrintClassHistogram 选项启动的, Ctrl-Break 处理将生成堆直方图histogram。

如果线程转储被获取了,runnable状态的线程的线程轨迹是个好的起点。线程转储的格式的更多信息见 2.15.1 节,还有线程转储里可能的线程状态表格。在有些情况下,可能需要获取一序列线程转储来确定哪个线程是持续繁忙的。

如果程序控制台不可得(进程运行在后台或VM输出被重定向到未知地方),jstack 工具可用于获取线程轨迹。用 jstack -F pid 选项来强制循环进程生成栈转储。从2.11节查看这个工具的输出的信息。jstack 工具也用于如果线程转储不能提供Java 线程循环的证据的情况。

当查看 jstack 工具的输出时,开始时专注于处于 RUNNABLE 状态的线程。这个状态很可能是因为线程是繁忙和循环。可能需要执行 jstack 多次来获得一个线程循环的完整图(译注:也就是通过对比一序列转储来确定)。如果一个线程总是表现为 RUNNABLE 状态, -m 选项可用于打印本地帧和提供线程在做什么的更多提示。如果线程在 RUNNABLE 状态表现出持续循环,这个情况指示了一个潜在的 HotSpot VM bug,需要进一步调查。

如果VM不响应 Ctrl-\,这可能指示了VM bug而不是程序或库代码的问题issue。在这种情况下, jstack -m -F 可用于获取所有线程的栈。这个输出将包括VM内部线程的栈。在这个栈轨迹里,标识那些没有表现出等待的线程。

5.2 诊断挂起进程

如果程序表现出挂起且进程表现出空闲,那么第一步是尝试获取线程转储。如果程序控制台可得,按下 Ctrl-\ (Solaris OS 或 Linux上)或 Ctrl-Break (Windows上)引起 HotSpot VM 打印线程转储 。在Solaris OS 和Linx上,线程转储还可以通过发送 SIGQUIT 到进程(命令: kill -QUIT <pid>)来获得。

5.2.1 检测到死锁

如果挂起进程能够生成线程转储,输出将被打印到目标进程的标准输出。打印线程转储之后,HotSpot VM 执行一个死锁检测算法。如果检测到死锁,死锁将与涉及死锁的线程的栈轨迹一起输出。下面是输出示例:

  Found one Java-level deadlock:
=============================
"AWT-EventQueue-0":
  waiting to lock monitor 0x000ffbf8 (object 0xf0c30560, a java.awt.Component$AWTTreeLock),
  which is held by "main"
"main":
  waiting to lock monitor 0x000ffe38 (object 0xf0c41ec8, a java.util.Vector),
  which is held by "AWT-EventQueue-0"

Java stack information for the threads listed above:
===================================================
"AWT-EventQueue-0":
        at java.awt.Container.removeNotify(Container.java:2503)
        - waiting to lock <0xf0c30560> (a java.awt.Component$AWTTreeLock)
        at java.awt.Window$1DisposeAction.run(Window.java:604)
        at java.awt.Window.doDispose(Window.java:617)
        at java.awt.Dialog.doDispose(Dialog.java:625)
        at java.awt.Window.dispose(Window.java:574)
        at java.awt.Window.disposeImpl(Window.java:584)
        at java.awt.Window$1DisposeAction.run(Window.java:598)
        - locked <0xf0c41ec8> (a java.util.Vector)
        at java.awt.Window.doDispose(Window.java:617)
        at java.awt.Window.dispose(Window.java:574)
        at javax.swing.SwingUtilities$SharedOwnerFrame.dispose(SwingUtilities.java:1743)
        at javax.swing.SwingUtilities$SharedOwnerFrame.windowClosed(SwingUtilities.java:1722)
        at java.awt.Window.processWindowEvent(Window.java:1173)
        at javax.swing.JDialog.processWindowEvent(JDialog.java:407)
        at java.awt.Window.processEvent(Window.java:1128)
        at java.awt.Component.dispatchEventImpl(Component.java:3922)
        at java.awt.Container.dispatchEventImpl(Container.java:2009)
        at java.awt.Window.dispatchEventImpl(Window.java:1746)
        at java.awt.Component.dispatchEvent(Component.java:3770)
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:463)
        at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:214)
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:163)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:157)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:149)
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:110)
"main":
        at java.awt.Window.getOwnedWindows(Window.java:844)
        - waiting to lock <0xf0c41ec8> (a java.util.Vector)
        at javax.swing.SwingUtilities$SharedOwnerFrame.installListeners(SwingUtilities.java:1697)
        at javax.swing.SwingUtilities$SharedOwnerFrame.addNotify(SwingUtilities.java:1690)
        at java.awt.Dialog.addNotify(Dialog.java:370)
        - locked <0xf0c30560> (a java.awt.Component$AWTTreeLock)
        at java.awt.Dialog.conditionalShow(Dialog.java:441)
        - locked <0xf0c30560> (a java.awt.Component$AWTTreeLock)
        at java.awt.Dialog.show(Dialog.java:499)
        at java.awt.Component.show(Component.java:1287)
        at java.awt.Component.setVisible(Component.java:1242)
        at test01.main(test01.java:10)

Found 1 deadlock.

默认的死锁检测可以和通过 synchronized 关键字获取的锁,还有通过 java.util.concurrent 包获取的锁一起工作。如果设置了 JVM 的 -XX:+PrintConcurrentLocks 标记,那么栈轨迹也显示锁属主的列表。

如果检测到死锁,你必须更详细地检验来理解死锁。在上面的例子里,线程 main 锁定了对象 <0xf0c30560>,并等待进入 0xf0c41ec8,它是由线程 AWT-EventQueue-0 锁定的。然而,线程 AWT-EventQueue-0 正在等待 0xf0c30560,而它是由线程 main 锁定。

栈轨迹里的详细信息提供了查找死锁的帮助。

5.2.2 未检测到死锁

如果线程转储被打印且没有发现死锁,那么问题可能是个bug,线程在监视器上等待但从未收到通知。这可能是个定时timing问题或一般的逻辑错误。

为找出问题的更多信息,检查线程转储里的每个线程和每个阻塞在 Object.wait() 的线程。栈轨迹里的调用帧指示了正在调用 wait() 的类和方法。如果代码编译时(默认)包含了行号信息,这直接提供了检查的方向(译注:可以直接看到哪行源码在调用 wait 方法)。大多数情况下,为进一步诊断问题,你必须有程序逻辑或库的知识。通常,你必须明白程序的同步是如何工作的,特别是监视器何时、何地被通知的细节和条件。

5.2.3 没有线程转储

如果VM不响应 Ctrl-\Ctrl-Break ,可能是VM死锁了或由于其它原因挂起了。在那样的情况下用 jstack 工具来获取线程转储。 jstack -F pid 选项来强制挂起进程的线程转储。这也可以用于于程序不能访问或输出被重定向到未知地方。

在 jstack 的输出里,检查每个处于 BLOCKED 状态的线程。顶层帧有时能指示线程为什么被阻塞了,例如 Object.waitThread.sleep 。栈的其余部分将给出线程正在做什么。特别是源码被编译了行号信息,你可以交叉引用源代码。

如果线程处于 BLOCKED 状态且理由不明显,用 -m 选项来获取一个混合栈。在混合栈的输出里,你应该可以确定线程为什么被阻塞了。如果线程因为尝试进入同步方法或块而阻塞,你将在顶层帧附近看到类似 ObjectMonitor::enter 的帧。举例:

  ----------------- t@13 -----------------
0xff31e8b8      ___lwp_cond_wait + 0x4
0xfea8c810      void ObjectMonitor::EnterI(Thread*) + 0x2b8
0xfeac86b8      void ObjectMonitor::enter2(Thread*) + 0x250
:

处于 RUNNABLE 状态的线程也可能阻塞。混合栈里的顶层帧应当指示线程正在做什么。

一个需要检查的特定线程是 VMThread。这个特定线程用于执行像垃圾回收的动作。它可以通过线程的初始栈是否正在执行 VMThread::run() 来鉴别。在Solaris OS上典型的是 t@4,在Linux上是用 C++ mangled名字 _ZN8VMThread4loopEv

通常,你可以从命令行执行程序,你可以得到VM不响应 Ctrl-\Ctrl-Break 的状态,很可能是你揭露了一个VM bug,一个线程库问题issue,或其他库的bug。如果这个发生,获取一个崩溃转储,尽可能多地收集信息,提交bug报告或支持电话。

在挂起进程上下文要提及的另一个工具是Solaris OS 的 pstack 工具。Linux上与 pstack 等价的工具是 lsstack。在写这篇文档时 lsstack 只能报告本地栈帧。

TODO。

5.3 Solaris 8 OS 线程库

TODO

相关 [java se 挂起] 推荐:

Java SE 7发布

- Jingzhi - Solidot
甲骨文宣布发布Java SE 7,这是Java在甲骨文名下发布的第一个版本. 开发者在博客上称它是五年全世界Java社区协助成果,是Java发展中的重要一步. Java SE 7主要新特性包括:Project Coin,提高生产力,简化编程任务;Fork/Join Framework,支持多核心处理器,简化问题分解并行执行;InvokeDynamic,使其它语言能更容易的在JVM中运行.

Java SE 7 Exception的使用

- - ITeye博客
Java SE 7 Exception的使用. 在Java SE 7 中,作为Project Coin项目中众多有用的细小语言变化之一的加强型异常处理,现在来学习如何利用它. 在这边文章中,我们所涉及的一些变化是作为Java平台标准版7(Java SE 7)所发布,在JSR334(Java Specification Request)有详细的说明.

Java SE 6 故障排除指南 – 5、挂起或循环进程故障排除

- - 码蜂笔记
本章为挂起或循环进程的故障排除在特定程序上提供了信息和指导. 问题在涉及挂起或循环进程时发生. 挂起可能因为多种原因发生,但经常是源于程序代码、API代码或库代码里的死锁. 挂起甚至是因为 HotSpot VM的bug. 有时候,一个表面上是挂起的可能是个循环. 例如,VM进程里的bug导致一个或多个线程进入死循环,会消耗掉所有可得CPU周期.

Java SE 6 故障排除指南 – 3、内存泄露

- - 码蜂笔记
如果你的应用程序执行的时间越来越长,或如果操作系统执行越来越慢,这可能是内存泄露的指示. 换句话说,虚拟内存被分配但在不需要时没有归还. 最终应用程序或系统没有可用内存,应用程序非正常终止. 这篇文章提供了一些涉及内存泄露的问题诊断的建议. 3.1 OutOfMemoryError 的含义. 一个最常见的内存泄露的指示是 java.lang.OutOfMemoryError 错误.

利用Java SE 8流处理数据II(译)

- - BlogJava-首页技术区
利用Java SE 8流处理数据. -- 结合Stream API的高级操作去表示富数据处理查询. 本文是 Java Magazine 201405/06刊中的一篇文章,也是文章系列"利用Java SE 8流处理数据"中的第二篇,它基于flatMap()和collect()介绍了Java流的高级用法(2014.08.15最后更新).

[译]Java SE 8 新特性之旅 : Java开发世界的大变动

- - 上善若水 厚德载物
我很自豪的成为了adopt-OpenJDK的一员,像其他专业团队成员一样,但是我只刚加入了8个月,我们一同经历了Java SE 8 的开发、编译、编码、讨论……等等,直到JDK上线. Java SE 8发布于2014年3月18日,现在可供下载使用. 我很高兴发布这一系列“Java SE 8 新特性之旅”,我会写一些例子来简化Java SE 8知识的获取、开发经验、新特性和API,然后.

Java SE 6 故障排除指南 – 4、系统崩溃故障排除

- - 码蜂笔记
崩溃或致命错误导致进程异常终止. 例如,崩溃可能是由于HotSpot VM、系统库、Java SE 库或API、程序本地代码、甚至操作系统里的 bug. 极端因素如操作系统资源耗尽也可以导致崩溃. 因 HotSpot VM 或 Java SE库代码导致的崩溃是罕见的. 有时候可以变通崩溃直到导致崩溃的源被诊断和修复(也就是可以避开崩溃).

Oracle Java SE 8 发行版更新:限制商业或生产用途

- - 开源中国社区最新新闻
Oracle Java SE 8 发行版更新. Oracle Java SE 8 的公开更新仍面向单独的个人使用提供,至少持续至 2020 年底. 2019 年 1 月以后发布的 Oracle Java SE 8 公开更新将不向没有商用许可证的业务、商用或生产用途提供. 如果您是使用者,将 Java 用于单独的个人用途,则至少在 2020 年底之前,您对 Oracle Java SE 8 更新仍具有与现在相同的访问权限.

Spring Framework 4.0相关计划公布---包括对于Java SE 8 和Groovy2的支持

- - InfoQ cn
VMware公司旗下的SpringSource团队近日宣布了Spring Framework 4.0的相关计划,这是Spring框架的下一个升级版本,新的特性包括了对Java SE 8,Groovy 2,Java EE 7部分功能和WebSockets的支持. 在介绍Spring Framework 3.2版本的.

苹果发布2020年款iPhone SE 售价399美元

- - IT瘾-cnbeta
该机将于本周五(4月17日)开始预购,4月24日发货. 届时将有128GB机型提供,售价449美元,256GB机型售价549美元. 与其他iPhone一样,它将附带免费一年的Apple TV Plus,它将有黑色、白色和产品红三种颜色. iPhone SE本质上是一款拥有更好的摄像头和处理器的iPhone 8,价格也更低,虽然它沿用相对较老的设计, 但这款iPhone SE有 苹果的A13 Bionic芯片, 与最新的iPhone 11和11 Pro是同一个型号.