JVM 故障排查
约 3523 字大约 12 分钟
2026-05-24
前置准备
故障排查的基本原则和流程
核心原则:保护现场、最小影响、客观数据。
- 保护现场:若遇到 OOM 或者 CPU 飙升,若第一时间重启,则会破坏现场。重启或kill进程前,必须保留现场。包括:导出 Heap Dump、Thread Dump、GC 日志、系统监控截图、甚至保留一个故障节点不重启(如果集群有多个节点,可以隔离一个节点专门用于排查)。
- 最小影响:排查动作可能直接导致故障,应该评估排查命令的副作用。优先使用轻量级、无侵入的命令(如
jstat,jcmd,或 Arthas 的异步命令)。如果必须做重量级 Dump,尽量在流量低谷期,或者在已隔离的故障节点上进行。 - 客观数据:一切结论必须有数据支撑。CPU 高要看火焰图和线程栈,内存泄漏要看 MAT 的 dominator tree,GC 频要看 GC 日志。
流程:
- 发现问题
- 止血:通过降级、限流、扩容、重启(保护现场)等手段,先恢复业务正常运转。
- 定位:保留现场等数据,结合排查工具检查问题。
- 修复:修复问题后需要再测试环境验证。
- 复盘:可整理故障报告,避免同类问题再次发生,记录应对手段。
常见 JVM 故障类型分类
类型一:资源枯竭
- 堆内存溢出(Heap Space OOM):对象过多、内存泄漏、GC 无法回收等。
- 元空间溢出(Metaspace OOM):类加载过多导致元空间耗尽。
- 直接内存溢出(Direct Buffer OOM):NIO 或者 Netty 等,直接内存使用过多未释放。
- 线程资源耗尽(Unable to create new native thread):线程数超过操作系统限制。
类型二:性能瓶颈(很隐蔽)
- CPU 飙高:死循环、正则表达式回溯、频繁 GC 引起。
- GC 异常和停顿:频繁 GC、STW 过长、引起周期性卡顿。
- 线程阻塞和死锁:高并发下的锁竞争激烈、数据库链接满了、线程池满了、大量线程
BLOCKED、WAITING。
类型三:系统崩溃
- JVM 自身BUG:JVM 出现问题时,会留下
hs_err_pid.log日志。 - 被容器抹杀:在 Docker/K8s 环境下,内存超过容器限制,会被直接抹杀,没有任何 JVM 日志 。
- Safepoint 停滞:JVM 为了进行某些全局操作(如 GC、代码去优化)需要所有线程到达安全点,某些线程迟迟未到达导致整个 JVM 长时间卡死。
类型四:初始化异常
- 找不到类。
- Jar 包依赖冲突。
关键诊断参数与日志配置
OOM 与内存现场保留
这是故障排查的“底线配置”。没有这些,一旦发生 OOM,进程直接退出。
-XX:+HeapDumpOnOutOfMemoryError「必配」 当 JVM 抛出 OutOfMemoryError 时,自动将堆内存快照(Dump)保存到磁盘。
-XX:HeapDumpPath=<path>「必配」 指定 Dump 文件的保存路径。最佳实践:使用 %p (进程号) 和 %t (时间戳) 防止文件名冲突被覆盖。示例:/data/logs/jvm/heapdump_%p_%t.hprof
-XX:+HeapDumpOnCtrlBreak「选配」 在 Windows 环境下,允许通过 Ctrl+Break 手动触发 Heap Dump(Linux 下通常用 jmap 或 jcmd)
GC 日志配置
# 【必配】打印 GC 详细信息、时间戳、日期、应用停顿时间
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-XX:+PrintTenuringDistribution
-XX:+PrintGCApplicationStoppedTime
# 【必配】指定日志文件路径
-Xloggc:/data/logs/jvm/gc_%p.log
# 【必配】开启日志轮转(保留 5 个文件,每个最大 10MB)
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=10MJDK 9 之后,引入了统一日志框架,配置方法发生了变化。
# 【必配】JDK 11/17/21/25 统一 GC 日志配置
# 语法:-Xlog:<标签>:<输出文件>:<装饰器>:<选项>
-Xlog:gc*,gc+age=trace,safepoint:file=/data/logs/jvm/gc_%p.log:time,uptime,level,tags:filecount=5,filesize=10Mgc*,gc+age=trace,safepoint:记录所有 GC 事件、对象年龄追踪(排查对象过早晋升)、安全点停顿(排查非 GC 引起的 STW)。file=...:输出文件路径(支持%p和%t)。time,uptime,level,tags:日志装饰器(打印时间、JVM 运行时间、日志级别、标签)。filecount=5,filesize=10M:自带日志轮转,保留 5 个 10MB 的文件。
JVM 崩溃与底层诊断
这类参数用于排查非 Java 代码引起的故障,例如 JNI 本地代码崩溃、堆外内存泄漏、JIT 编译器 Bug。
-XX:ErrorFile=<path>「选配」 指定 JVM 致命错误日志(hs_err_pid.log)的生成路径。默认在启动目录,生产环境建议统一放到日志目录。
示例:/data/logs/jvm/hs_err_pid%p.log
-XX:NativeMemoryTracking=summary「选配」 看需求,排查堆外必配。目的:开启本地内存跟踪(NMT)。用于排查 DirectBuffer、Netty、NIO 引起的堆外内存泄漏。配置 summary 级别性能损耗极小(<2%),生产可常开;detail 级别损耗大,仅限排查时开启。
-XX:+UnlockDiagnosticVMOptions「选配」 解锁 JVM 高级诊断选项。很多底层的诊断命令(如打印 JIT 编译日志、查看汇编代码)需要此参数作为前置开关。
-XX:+PrintCompilation「选配」 打印 JIT 即时编译器的编译日志。用于排查代码“去优化”或 JIT 编译导致的 CPU 飙高问题(需配合上一条使用)。
现代 JVM
在高版本(特别是 JDK 17 和 21)中,JFR (Java Flight Recorder) 已经开源且免费。它比传统的 JVM 参数和 jmap/jstack 强大得多,且性能开销极低(<1%),推荐使用。
-XX:StartFlightRecording=<options>「必配」 启动时直接开启 JFR 记录。它可以记录 CPU、内存、GC、线程锁、I/O 等全方位数据,并生成 .jfr 文件,用 JDK Mission Control (JMC) 打开分析。
Options 说明,Options 是一个由逗号(
,)分隔的键值对(Key-Value)集合。核心参数
k/v 说明 filename=/data/logs/jfr/app_%p.jfr「必配」 指定输出路径,%p 进程号避免多实例覆盖。 settings=default/profile「必配」 指定采集模板。 default:开销极低(<1%),适合生产环境长期开启。profile:采集更详细的事件(如方法采样),开销略大,适合排查性能瓶颈时临时开启。duration=60s/5m/0「选配」 录制持续时间。如果不填或设为 0,则表示“无限期持续录制”(直到手动停止或进程退出)。delay=2m「选配」 延迟启动时间,避免收集到无用的启动噪音。 disk=true/false「选配」 是否将数据持久化到磁盘。生产环境必须为 true。maxsize=200M/1G限制 JFR 文件的最大体积。当达到上限时,JFR 会自动覆盖最旧的数据。 dumponexit=true/false「必配」 当 JVM 进程正常或异常退出(如 OOM、Crash)时,自动将内存中的 JFR 数据 Dump 到 filename指定的文件中。name=ProdRecording给这次录制起个名字,方便后续用 jcmd命令管理(如停止、导出)。
-XX:+FlightRecorder「选配」 仅初始化 JFR 引擎,但不立即记录。后续可通过 jcmd 命令动态开启录制。
JDK 21 引入了虚拟线程。虚拟线程调度问题,可通过 JFR 中的 jdk.VirtualThreadPinned 等事件来排查 “虚拟线程被平台线程固定(Pinned)” 导致的性能下降。
诊断参数模板
# 1. OOM 保护
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/logs/jvm/heapdump_%p_%t.hprof
# 2. GC 日志 (JDK 8 专属)
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-XX:+PrintTenuringDistribution
-XX:+PrintGCApplicationStoppedTime
-Xloggc:/data/logs/jvm/gc_%p.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=10M
# 3. 崩溃与底层
-XX:ErrorFile=/data/logs/jvm/hs_err_pid%p.log
-XX:NativeMemoryTracking=summary# 1. OOM 现场保护 (必配)
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/logs/jvm/heapdump_%p.hprof
# 2. GC 日志配置 (必配,JDK 9+ 统一日志框架)
# 记录 GC 事件、对象年龄、安全点;输出到文件;包含时间/运行时间/级别标签;轮转保留 10 个 20M 文件
-Xlog:gc*,gc+age=trace,safepoint:file=/data/logs/jvm/gc_%p.log:time,uptime,level,tags:filecount=10,filesize=20M
# 3. 崩溃与底层诊断 (推荐)
-XX:ErrorFile=/data/logs/jvm/hs_err_pid%p.log
-XX:NativeMemoryTracking=summary
# 4. JFR 持续飞行记录 (高版本必配)
# 采用单行写法以兼容所有 shell 启动脚本;使用 default 低开销模板;最大 250M 环形覆盖;退出时自动 dump
-XX:StartFlightRecording=name=ProdJFR,settings=default,disk=true,maxsize=250M,dumponexit=true,filename=/data/logs/jvm/flight_%p.jfr监控与诊断工具体系
JDK 自带命令行工具
进程与状态探查(轻量级,无副作用)
jps(Java Process Status)- 作用:查看当前系统中所有正在运行的 HotSpot 虚拟机进程(PID)及主类名称。是所有排查命令的第一步。
- 版本:全版本通用。
- 案例:
jps -l(输出主类全名/jar包路径),jps -v(输出 JVM 启动参数)。
jstat(JVM Statistics Monitoring)- 作用:实时监控 JVM 的各种运行状态信息,特别是 GC(垃圾回收)和类加载 数据。
- 版本:全版本通用。
- 案例:
jstat -gcutil <pid> 1000(每秒打印一次 GC 摘要,排查频繁 Full GC 必用)。
jinfo(Configuration Info)- 作用:实时查看和动态调整 JVM 配置参数(如查看
MaxHeapSize,动态开启/关闭PrintGCDetails)。 - 版本:全版本通用。但在高版本中,部分参数的动态修改受到了严格限制。
- 作用:实时查看和动态调整 JVM 配置参数(如查看
线程与内存剖析(重量级,需谨慎)
jstack(Stack Trace)- 作用:生成虚拟机当前时刻的线程快照(Thread Dump)。用于排查死锁、线程阻塞、CPU 飙高(结合
top -H)。 - 版本:全版本通用。
- 注意:在 JDK 21 引入虚拟线程后,
jstack对虚拟线程的展示支持在不断优化,但分析虚拟线程更推荐使用 JFR。
- 作用:生成虚拟机当前时刻的线程快照(Thread Dump)。用于排查死锁、线程阻塞、CPU 飙高(结合
jmap(Memory Map)作用:生成堆转储快照(Heap Dump,
.hprof文件),或查看堆内存对象直方图。版本:全版本通用。⚠️ 注意:在 JDK 9 中引入了
jhsdb jmap作为底层替代,但常规jmap命令依然保留。避坑:执行
jmap -dump会触发 Full GC 并导致应用暂停(STW),严禁在流量高峰期对大内存生产节点直接执行。
时代的眼泪与现代全能王
- ❌
jhat(JVM Heap Analysis)- 作用:内置的极简 Web 服务器,用于分析
jmap导出的.hprof文件。 - 版本:JDK 9 废弃,JDK 11 彻底移除。
- 现状:分析能力极弱且极其消耗内存,已被 MAT 和 JMC 完全取代。
- 作用:内置的极简 Web 服务器,用于分析
- 👑
jcmd(Java Command)- 作用:现代 JVM 诊断利器。它可以向 JVM 发送诊断命令,几乎能替代
jmap、jstack、jinfo的大部分功能,且对 JVM 的性能影响更小。 - 版本:JDK 7 引入,JDK 9+ 推荐作为首选工具。
- 实战:
jcmd <pid> GC.heap_dump /path/to/dump.hprof(替代jmap -dump)jcmd <pid> Thread.print(替代jstack)jcmd <pid> VM.flags(查看参数)
- 作用:现代 JVM 诊断利器。它可以向 JVM 发送诊断命令,几乎能替代
- ❌
早期自带的 GUI 工具
jconsole&jvisualvm- 版本变迁:在 JDK 8 中,它们是 JDK 自带的图形化监控工具。但从 JDK 9 开始,
jvisualvm被从 JDK 中剥离,需要去官网独立下载;jconsole虽然还在,但功能已落后。 - 现状:生产环境排查极少使用,已被替代。
- 版本变迁:在 JDK 8 中,它们是 JDK 自带的图形化监控工具。但从 JDK 9 开始,
第三方专业工具
👑 MAT (Memory Analyzer Tool)
- 定位:堆内存分析(Heap Dump)的绝对霸主。由 Eclipse 基金会维护。
- 适用场景:分析
jmap或 OOM 自动导出的.hprof文件。 - 核心功能:通过 Dominator Tree(支配树) 和 Leak Suspects(泄漏疑点分析),可在一分钟内从几十个 G 的内存快照中,精准揪出是哪个对象的哪条引用链导致了内存泄漏。全版本通用。
- 官网下载地址:https://eclipse.dev/mat/download/
👑 Arthas (阿尔萨斯)
- 定位:阿里开源的 Java 线上诊断利器,目前国内互联网公司的标配。
- 适用场景:不重启应用、不修改代码,直接在线上环境动态排查问题。
- 核心功能:
dashboard:实时系统面板。trace:方法内部调用耗时追踪(排查接口慢)。watch:实时观察方法的入参和返回值(线上抓 Bug 神器)。profiler:生成 CPU 或内存的火焰图(Flame Graph)。
- 版本:支持 JDK 6 - 21+。
- 官网:https://arthas.aliyun.com/
JProfiler
- 定位:商业收费诊断利器。
- 适用场景:本地开发环境或测试环境的深度性能调优。
- 优势:界面华丽,功能大而全(CPU、内存、线程、数据库探针、JPA 探针等)。若有预算,它是本地调优体验最好的工具;但线上生产环境更倾向于使用轻量级的 Arthas 和 JFR。
🚧 故障排查模拟-内存
内存泄漏排查:请看:故障排查-内存-内存泄漏
OOM(OutOfMemoryError)排查
内存使用异常分析
🚧 故障排查模拟-GC
GC 日志分析
Full GC 频繁问题
GC 停顿时间过长
🚧 故障排查模拟-线程与并发
线程死锁排查
线程阻塞与等待
CPU 使用率过高
🚧日志与转储分析
故障排查章节:
类加载故障:如 NoClassDefFoundError、Jar 包冲突、动态生成类导致的元空间异常。
JVM 底层与崩溃** (Crash)**:JVM 自身 Bug、JNI/C++ 代码段错误导致的 hs_err_pid.log 崩溃。
容器化特有故障:Docker/K8s 环境下的 OOMKilled(进程被系统强杀,无 JVM 日志)、CPU Throttling(CPU 限流导致卡顿)。
🚧 性能调优
JVM 参数调优
GC 策略选择与调优
最佳实践与预防
版权所有
版权归属:FelixJY
