【JVM】相关排查命令
- 【一】JVM 故障排查全套 Linux 命令、参数、实战案例
- 【1】前置说明
- (1)jps:定位 Java 进程 PID(入口命令)
- (2)top /htop:定位 CPU 占用高的 Java 线程
- 1-top(系统自带,无需额外安装)
- 2-htop(可视化,需 yum install htop)
- (3)jstack:打印线程堆栈,排查 CPU 高、死锁、阻塞
- (4)jstat:轻量实时监控 GC、分代内存(线上最常用,无性能损耗)
- 1-jstat -gc PID 间隔毫秒 打印次数
- 2-jstat -gccapacity PID 1000
- 3-jstat -gcmetacapacity PID 1000
- 4-jstat -gcutil PID 1000
- (5)jmap:堆内存快照、查看内存占用(排查 OOM)
- 1-jmap -heap PID:轻量查看堆分代配置,不生成 dump
- 2-jmap -histo PID:轻量堆对象统计(不拉完整 dump)
- 3-jmap -dump:format=b,file=xxx.hprof PID 生成堆快照
- 4-jmap -clstats PID:类加载统计,排查元空间
- (6)free /df/vmstat:系统底层资源排查
- 【二】GC 日志完整分析方法:区分新生代 / 老年代 / 元空间问题
- 【1】JVM GC 日志开启参数(生产必配)
- 【2】GC 日志关键字段解读
- (1)Young GC 日志样例
- (2)Full GC 日志样例(危险)
- 【3】如何判断哪个区域出问题
- (1)新生代 Eden 问题
- (2)老年代 Old Gen 问题(堆内存泄漏)
- (3)元空间 Metaspace 问题(元空间泄漏)
- (4)补充区分特征
- 【三】堆内存监控:轻量分析方案(不生成 dump 大文件)
- 方案 1:jstat 持续监控(生产首选,零 IO、低性能损耗)
- 方案 2:jmap -heap 查看分代配置与实时占用
- 方案 3:jmap -histo 轻量对象统计(定位泄漏对象)
- 方案 4:可视化轻量工具 Arthas(阿里开源,推荐线上)
- 【四】Heap Dump(hprof)快照是否包含元空间数据?
- 细分说明
- 如果要完整分析元空间问题配套方案
- 补充:OOM 元空间时仅靠 hprof 不够
- 【五】完整排查流程总结
【一】JVM 故障排查全套 Linux 命令、参数、实战案例
【1】前置说明
所有命令依赖 JDK 自带工具包(jps/jstack/jstat/jmap/jhat),需和运行 Java 进程同版本 JDK,且进程同用户。
(1)jps:定位 Java 进程 PID(入口命令)
作用:列出本机所有 Java 进程、PID、主类名、JVM 参数
常用参数
jps -l:输出完整主类全限定名(最常用)jps -m:输出 main 方法入参jps -v:输出进程启动全部 JVM 参数(Xmx/Xss/ 元空间参数等)jps -q:只输出 PID,脚本批量处理用
实战案例
# 查看所有Java进程完整类名jps-l# 输出示例12568com.server.Application9872org.apache.tomcat.startup.Bootstrap# 查看进程12568的所有JVM启动参数jps-v12568(2)top /htop:定位 CPU 占用高的 Java 线程
1-top(系统自带,无需额外安装)
作用:查看整机 CPU、内存,找到高 CPU PID;再定位内部高消耗线程
核心操作与参数
top# 进入面板Shift + P# 按CPU使用率排序Shift + M# 按内存占用排序1# 展开多核CPU完整排查 CPU 飙高流程案例
# 1. top 找到Java进程PID=12568top# 2. 单独只监控该进程top-p12568# 3. 导出该进程下所有线程(nid十六进制,用于jstack匹配)top-H-p12568# 4. 假设高CPU线程nid=3241,转16进制(printf "%x\n" 3241)printf"%x\n"3241# 输出 c91# 5. jstack打印堆栈,搜索0xc91定位死循环/频繁GC线程jstack12568>thread.loggrep-A20c91 thread.log2-htop(可视化,需 yum install htop)
htop-p12568# F6 按CPU排序,直接看线程占用(3)jstack:打印线程堆栈,排查 CPU 高、死锁、阻塞
作用:导出所有线程栈,定位死循环、死锁、阻塞、GC 线程耗时
常用参数
jstack PID:普通堆栈jstack -l PID:额外输出锁持有信息(排查死锁必用)jstack -F PID:进程假死、阻塞无响应时强制 dump 线程
实战案例
# 导出线程栈到文件jstack-l12568>jstack_$(date+%Y%m%d_%H%M).log# 连续3次打印堆栈,对比线程状态(排查持续CPU高)jstack12568>>stack1.logsleep2jstack12568>>stack2.logsleep2jstack12568>>stack3.log# 对比同一线程是否一直RUNNABLE死循环常见问题关键字:
RUNNABLE循环业务代码 → 业务死循环 CPU 高WAITING / BLOCKED→ 锁竞争、死锁GC Thread大量占用 → GC 频繁导致 CPU 飙升
(4)jstat:轻量实时监控 GC、分代内存(线上最常用,无性能损耗)
核心三大用法:jstat -gc/-gccapacity/-gcmetacapacity
1-jstat -gc PID 间隔毫秒 打印次数
作用:实时输出新生代、老年代、元空间内存、GC 次数、耗时(轻量监控首选,无需 dump)
字段说明
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT S0C/S1C:Survivor总容量;S0U/S1U:已使用 EC/EU:Eden总容量/已用 OC/OU:老年代总容量/已用 MC/MU:元空间总容量/已使用 YGC/YGCT:YoungGC总次数、总耗时 FGC/FGCT:FullGC总次数、总耗时 GCT:GC总耗时实战案例
# 每500ms打印一次GC状态,持续输出jstat-gc12568500# 每1秒打印,输出20次后停止jstat-gc12568100020判断问题技巧:
- OU(老年代已用)持续上涨,FGC 持续增多 → 堆内存泄漏
- MU(元空间已用)只涨不跌,FGC 频繁 → 元空间泄漏
- YGC 次数极多,YGCT 耗时高 → Eden 过小,分配对象过快
- FGCT 持续走高,每次 FullGC 停顿久 → 老年代不足 / 元空间达阈值
2-jstat -gccapacity PID 1000
查看各代内存容量、最大阈值(Xmx、MaxMetaspaceSize 生效值)
3-jstat -gcmetacapacity PID 1000
专门监控元空间:MC/MU、MCMX(最大元空间)、类加载卸载数量,排查元空间泄漏专用
4-jstat -gcutil PID 1000
百分比展示内存使用率,直观
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT0.0088.3212.4576.2392.1289.11321015.2104228.32043.530O=76%、M=92% 代表老年代、元空间占用极高,马上 OOM
(5)jmap:堆内存快照、查看内存占用(排查 OOM)
1-jmap -heap PID:轻量查看堆分代配置,不生成 dump
作用:打印堆整体配置、新生代 / 老年代 / 元空间容量、收集器类型,轻量无 IO 开销
案例:
jmap-heap12568# 输出:Xms/Xmx、Eden/Survivor比例、MaxTenuringThreshold、Metaspace配置、GC收集器2-jmap -histo PID:轻量堆对象统计(不拉完整 dump)
作用:实时打印堆内所有对象类型、实例数量、占用内存大小,快速定位大对象 / 泄漏对象
# 输出到文件分析jmap-histo12568>histo.log# 只看前30行占用最大的对象head-30histo.log字段:序号 | 实例数量 | 占用字节 | 类名
发现自定义业务类持续大量创建 = 堆泄漏
3-jmap -dump:format=b,file=xxx.hprof PID 生成堆快照
作用:导出完整堆 dump 文件,用于 MAT 深度分析
# 导出堆快照到指定路径jmap-dump:format=b,file=/data/heap_$(date+%Y%m%d).hprof12568# OOM自动dump参数(启动JVM时配置,无需手动执行命令)-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/data/dump4-jmap -clstats PID:类加载统计,排查元空间
输出加载类总数、卸载数量、元空间占用
(6)free /df/vmstat:系统底层资源排查
free-h# 整机物理内存、Swap,Swap持续上涨说明内存不足vmstat1# 每秒系统IO、内存、CPU,si/so代表swap交换频繁(GC卡顿)df-h# 磁盘满会导致dump文件无法生成、GC日志写失败【二】GC 日志完整分析方法:区分新生代 / 老年代 / 元空间问题
【1】JVM GC 日志开启参数(生产必配)
# 基础GC日志-XX:+PrintGCDetails-XX:+PrintGCTimeStamps-XX:+PrintGCDateStamps-Xloggc:/data/logs/gc-%t.log# 打印元空间、类卸载日志-XX:+PrintMetaspaceStatistics-XX:+TraceClassUnloading# 打印GC前后各代内存-XX:+PrintHeapAtGC【2】GC 日志关键字段解读
(1)Young GC 日志样例
2026-07-17T10:00:00.123+0800:12345.123:[GC(Allocation Failure)[PSYoungGen: 81920K->800K(92160K)]120000K->40880K(194560K),0.012345secs][Metaspace: 32100K->32100K(108032K)]拆解:
Allocation Failure:Eden 区满,分配新对象失败,触发 YoungGCPSYoungGen: 81920K->800K:新生代 GC 前 80M,回收后 0.8M,回收效果好- 整体堆:120000K→40880K
Metaspace:32100K->32100K:元空间无变化,本次不处理类卸载
(2)Full GC 日志样例(危险)
2026-07-17T10:01:00.456+0800:12400.456:[Full GC(Metadata GC Threshold)[PSYoungGen: 6000K->0K(92160K)][ParOldGen: 80000K->78000K(102400K)]86000K->78000K(194560K),[Metaspace: 45000K->44800K(108032K)],0.567890secs]触发原因Metadata GC Threshold:元空间占用达到 MetaspaceSize 阈值,强制 FullGC
【3】如何判断哪个区域出问题
(1)新生代 Eden 问题
日志特征:
- 频繁
GC (Allocation Failure),YGC 每分钟几十上百次 - Eden 区 EU 快速打满,每次 YGC 回收大量内存但很快再次占满
根因:
- 对象创建速度过快(接口并发高、循环创建临时对象)
-Xmn新生代内存设置过小
现象:YGC 频繁,轻微 STW,CPU 小幅升高
(2)老年代 Old Gen 问题(堆内存泄漏)
日志特征:
- 每次 YGC 后 OU(老年代已用)持续阶梯上涨
- FullGC 次数逐步增多
- FullGC 前后老年代内存几乎无下降:
ParOldGen:80000K->79500K - 最终抛出
Java heap space OOM
根因:静态集合、ThreadLocal、缓存未清理,对象长期存活晋升老年代无法回收
(3)元空间 Metaspace 问题(元空间泄漏)
日志关键字:
- FullGC 触发原因:
Metadata GC Threshold/Metaspace allocation failure - GC 日志中
Metaspace: 42000K->41980K,回收几乎无释放 - jstat -gc MU 持续单向上涨,类卸载日志极少或无
根因:热部署、CGLIB 动态代理、自定义 ClassLoader 未释放,类无法卸载
最终报错:java.lang.OutOfMemoryError: Metaspace
(4)补充区分特征
表格
| 故障区域 | GC 核心特征 | jstat 表现 |
|---|---|---|
| 新生代 | YGC 极其频繁,OU 稳定 | EU 快速起落,OU 平稳 |
| 老年代泄漏 | FGC 持续增加,FullGC 回收空间极少 | OU 只涨不跌 |
| 元空间泄漏 | 大量 FullGC 由 Metadata 阈值触发 | MU 持续上涨,无回落 |
【三】堆内存监控:轻量分析方案(不生成 dump 大文件)
方案 1:jstat 持续监控(生产首选,零 IO、低性能损耗)
组合命令实时观测三代内存变化,无需快照:
# 每1秒输出GC使用率+元空间状态jstat-gcutil125681000# 单独监控元空间jstat-gcmetacapacity125681000适用场景:线上持续观测内存走势,判断是否泄漏。
方案 2:jmap -heap 查看分代配置与实时占用
瞬时打印新生代、老年代、元空间总容量、当前使用量,无文件生成,秒级返回。
方案 3:jmap -histo 轻量对象统计(定位泄漏对象)
导出所有对象数量与内存占用,文件远小于 hprof 快照,快速定位自定义业务大对象:
jmap-histo12568>histo.log# 过滤业务自定义类grep"com.yourpackage"histo.log方案 4:可视化轻量工具 Arthas(阿里开源,推荐线上)
无需重启进程,attach 后实时监控分代、GC、对象,无大 dump 文件
核心命令:
# 查看堆各区域实时占用memory# 实时GC监控gc-i1000# 统计对象创建排行heapdump--live/tmp/tmp.hprof# 可选只dump存活对象,文件更小优势:支持在线查看类加载数量、元空间占用、线程状态,不用记忆大量 Linux 原生命令。
【四】Heap Dump(hprof)快照是否包含元空间数据?
结论:标准jmap -dump堆快照不包含完整元空间类元数据
细分说明
- hprof 包含内容:
- Java 堆所有对象、数组、Class 实例(堆中的
java.lang.Class对象) - 静态变量、ThreadLocal、字符串常量池、对象引用链
- Java 堆所有对象、数组、Class 实例(堆中的
- hprof不包含:
- 元空间底层原始类元数据(字节码、运行时常量池、方法表、字段信息)
- ClassLoader、类加载卸载统计、本地内存 Chunk 分配信息
如果要完整分析元空间问题配套方案
- 轻量排查元空间:
jstat -gcmetacapacity+jmap -clstats+ GC 日志-XX:+TraceClassUnloading - 深度分析类加载泄漏:
- 导出 GC 日志看类卸载记录
- 使用 Arthas
classloader命令查看各加载器加载类数量 - MAT 打开 hprof 可查看堆内 ClassLoader 实例,但看不到元空间底层字节码数据
补充:OOM 元空间时仅靠 hprof 不够
元空间泄漏根源是 ClassLoader 无法回收,hprof 只能看到堆里的 ClassLoader 对象,完整判断必须配合 GC 日志、类加载统计、jstat 元空间监控三者结合。
【五】完整排查流程总结
jps -l获取 Java PID- CPU 飙高:
top -H定位高 CPU 线程 →jstack匹配线程栈,区分业务死循环 / GC 线程消耗 - 频繁 FullGC / 内存缓慢上涨:
jstat -gcutil 1000持续观测新生代、老年代、元空间走势 - 怀疑堆泄漏:
jmap -histo轻量分析对象,确认泄漏后生成 hprof 快照用 MAT 分析 - 怀疑元空间泄漏:查看 GC 日志 Metadata 触发的 FullGC、
jstat -gcmetacapacity、类卸载日志 - OOM 事后复盘:启用
-XX:+HeapDumpOnOutOfMemoryError自动生成 dump,同时留存完整 GC 日志