.. _debug_java:
=======================
排查Java程序
=======================
.. note::
虽然我不懂Java开发,但是运维工作中还是会遇到一些Java程序运行异常问题需要排查,例如CPU过高,负载过高等。对于Java程序简单排查可以方便我们定位问题。
Java程序和线程
=================
Java程序有时候会出现挂起和运行缓慢的问题,通过线程dump可以提供一个当前运行的java集成的状态快照,方便进行分析诊断。这种方式简单有效,经常能够发现一些线索以及简单的错误。
Java使用线程来执行每个内部和外部操作。Java的垃圾搜集进程有自己的线程,Java应用程序内部的任务也会创建自己的线程。
在Java程序的生命周期中,线程会经历多种状态。每个线程都有一个跟踪当前操作的执行堆栈。此外,JVM还存储了之前调用成功的所有方法。因此,分析完整的堆栈可以帮助我们研究出现问题时应用程序发生了什么。
.. figure:: ../_static/performance/Life_cycle_of_a_Thread_in_Java.jpg
:scale: 70
Java线程生命周期
.. csv-table:: Java线程生命周期
:file: debug_java/java_thread_lifecycle.csv
:widths: 20, 80
:header-rows: 1
获取Java程序线程dump
=====================
一旦Java程序开始运行,有多种方式来生成Java线程dump用于分析诊断:
- JVM Process Status(jps)
- jstack
- 首先使用 ``top`` 命令检查系统中运行的Java程序,找出进程 ``PID`` ; 也可以使用 ``jps`` 命令找出Java进程PID:
.. literalinclude:: debug_java/jps
:language: bash
:caption: 使用 ``jps`` 命令输出运行的java进程PID
输出类似:
.. literalinclude:: debug_java/jps_output
:language: bash
:caption: 使用 ``jps`` 命令输出运行的java进程PID
- 记录下Java进程的所有LWP线程:
.. literalinclude:: debug_java/ps_lwp
:language: bash
:caption: 使用 ``ps`` 命令输出运行的java进程线程
执行 ``cat OS_LWP | sort -k3`` 可以看到按线程CPU繁忙程度排序的结果:
.. literalinclude:: debug_java/ps_lwp_output
:language: bash
:caption: 按CPU繁忙程度排序java线程
.. note::
在 ``jstack`` 中记录的线程 ``nid`` 是十六进制,所以这里 ``OS_LWP`` 的PID需要从十进制转换成十六进制来对应
- 执行 ``jstack`` 命令获取Java线程堆栈:
.. literalinclude:: debug_java/jstack_pid
:language: bash
:caption: jstack命令输出java进程PID的堆栈
获得 ``jstack.txt`` 进行下一步分析
分析java线程dump
==================
- 在 ``jstack.txt`` 的前面部分可能有类似如下内容,显示了JVM版本以及 Safe Memory Reclamation (SMR) 和 non-JVM internal threads::
2021-01-04 12:59:29
Full thread dump OpenJDK 64-Bit Server VM (15.0.1+9-18 mixed mode, sharing):
Threads class SMR info:
_java_thread_list=0x00007fd7a7a12cd0, length=13, elements={
0x00007fd7aa808200, 0x00007fd7a7012c00, 0x00007fd7aa809800, 0x00007fd7a6009200,
0x00007fd7ac008200, 0x00007fd7a6830c00, 0x00007fd7ab00a400, 0x00007fd7aa847800,
0x00007fd7a6896200, 0x00007fd7a60c6800, 0x00007fd7a8858c00, 0x00007fd7ad054c00,
0x00007fd7a7018800
}
- 然后是完整的线程列表dump,包含了以下信息:
.. csv-table:: Java线程dump ( ``jstack`` )输出内容字段说明
:file: debug_java/jstack_info.csv
:widths: 20, 80
:header-rows: 1
分析关注点
------------
- 在 ``jstack.txt`` 最后显示 Java 本机接口(JNI)引用。当发生内存泄漏时,需要特别关注,因为它们不会被自动垃圾回收::
JNI global refs: 15, weak refs: 0
- 重点关注 ``RUNNABLE`` 和 ``BLOCKED`` 线程,以及 ``TIMED_WAITING`` 线程:
- 这些状态会提示我们两个或多个线程之间发生冲突的方向
- **在死锁情况下,多个线程运行在共享对象上持有一个同步块** ( ``In a deadlock situation in which several threads running hold a synchronized block on a shared object`` )
- **在线程争用时,一个线程会阻塞等待其他线程完成** ( ``In thread contention, when a thread is blocked waiting for others to finish`` )
- 对于异常高的CPU使用率,通常我们只需要查看 ``RUNNABLE`` 线程
- 除了 ``jstack`` 获取线程dump,还有一个常用命令是 ``top -H -p PID`` ,可以显示哪些线程正在消耗特定进程中的操作系统资源
- 此外建议查看 ``jstack`` 输出中内部JVM线程,例如 ``GC``
- 当Java程序处理性能 **异常低** 时,应该检查 ``BLOCKED`` 线程
- 一些间歇性的性能问题,需要间隔很近的多次 ``jstack`` 获取线程堆栈,以便进行对比
- 推荐的最佳做法是 **至少进行3次 jstack dump** ,每 ``10秒`` 一次
- 建议在Java代码中对创建新线程使用命名以便在排查时能够定位源代码
- 忽略内部JVM处理(例如GC)
- 当出现异常的CPU或内存使用情况,应该关注长时间运行或阻塞的线程
- 使用 ``top -H -p PID`` 来检查线程堆栈和CPU处理的关系
在线分析工具
=============
- `FastThread `_
- `JStack Review `_ 本地浏览器内运行,推荐
- `Spotify Online Java Thread Dump Analyser `_ 开源的使用JavaScript编写的分析工具
分析工具
=========
- `JProfiler `_ 最强大的Java分析工具,提供10天试用license
- `IBM TMDA `_
- `Irockel TDA `_ 开源的Thread Dump Analyser(TDA)
参考
=======
- `How to Troubleshoot Java High CPU Usage Issues in Linux? `_
- `How to Analyze Java Thread Dumps `_
- `Life Cycle of a Thread in Java `_