跳到正文
Joeplover
学习笔记·2026-08-08·约 5 分钟阅读

JConsole 实战排查:CPU 飙升、服务假死与内存泄漏

用 JConsole 快速定位 Java 服务故障:CPU 满载找线程、服务假死查死锁与阻塞、内存泄漏看 GC 曲线、元空间泄漏盯类加载。总结一套连上就能用的排查顺序。

监控仪表盘上的性能曲线,对应 JConsole 排查 Java 服务问题

JConsole 实战排查:CPU 飙升、服务假死与内存泄漏

生产环境里 Java 服务出问题,第一反应往往是打开 JConsole 连上去看一眼。它不需要改代码、不需要重启,能实时看到内存、线程、CPU 的现场状态,适合做实时确诊和初步定性。这篇文章整理四类常见故障的排查路径,以及一套连上就能用的操作顺序。

一、CPU 突然飙升

目标:找到是哪个线程、在执行什么代码导致 CPU 满载。

1. 连接并进入【线程】标签页

启动 JConsole,选择目标进程连接后,直接进入【线程】标签页。

2. 找到嫌疑线程

点击表头"CPU 占用率"(部分版本支持)或凭经验,在用户线程里找长期处于 RUNNABLE 状态的线程。

注意看那些名字像 http-nio-8080-exec-* 的线程,通常是你自己的业务代码。框架自带的线程一般不会无缘无故把 CPU 打满,嫌疑集中在处理请求的工作线程上。

3. 导出堆栈定位代码

选中该线程,右侧会显示堆栈跟踪。

快速连续点击线程名或等待自动刷新,观察堆栈一直停留在哪一行代码。比如一直卡在某个 HashMap.get() 或正则表达式 Pattern.compile() 上,那就是问题代码。堆栈如果每次刷新都停在同一个位置,说明线程不是在做短暂计算,而是卡死在某段代码里反复执行。

4. 终极一步:top -Hp + jstack 精确到行号

如果 JConsole 线程名不够直观,回服务器用 top -Hp 找到 CPU 最高的本地线程 ID,转 16 进制后在 jstack 结果里搜索,能精确到代码行号。

top -Hp <pid>
# 记下 CPU 最高的 PID(线程的本地 ID)
printf '%x\n' <线程PID>
# 得到十六进制 nid,例如 0x1a3f
jstack <pid> | grep -A 30 '0x1a3f'

jstack 输出的 nid 就是十六进制线程 ID,直接搜就能看到该线程完整堆栈,定位到具体类和行号。

二、服务假死 / 请求无响应

目标:判断线程是死锁、全部阻塞,还是无限等待。

1. 先点【检测死锁】

进入【线程】标签页,先点【检测死锁】按钮。

如果弹窗了,恭喜,死锁被秒杀。它会列出互相等待的线程,直接看堆栈定位代码。

2. 没有死锁,看线程数量和状态分布

如果没有死锁,观察线程数量和状态分布,不同状态对应不同病因:

大量 BLOCKED 状态:说明存在激烈的锁竞争。选中一个 BLOCKED 线程,看它在等哪个锁对象,再查看持有该锁的线程的堆栈,找出谁长期持有锁不释放(可能执行了慢 SQL 或外部调用)。

大量 WAITING 状态:比如所有 http-nio 线程都在 WAITING,说明没请求进来;或者线程池里的线程全在等任务队列。结合线程名定位。

3. 一种可恢复的"假死"

如果线程全是 WAITING (parking),可能是在等待 Redis、MQ 等外部连接超时,堆栈里会暴露 SocketInputStream.socketRead0 等调用。这类假死不用重启服务,等外部依赖恢复或连接超时后,线程会自动继续。

三、内存泄漏 / OOM 风险

目标:观察 GC 效果,确认老年代是否持续增长不降。

1. 看堆内存曲线

进入【内存】标签页,看堆内存使用量曲线。

2. 手动触发 GC

点【执行 GC】按钮,观察曲线变化:

  • 正常情况:触发后,曲线会像锯齿一样陡降,老年代占用回到一个较低基线。
  • 内存泄漏:GC 后曲线只降一点点或几乎不动,老年代基线不断抬高,锯齿形状逐渐消失,越来越接近 -Xmx 设置的上限。

3. 确认趋势

等待业务高峰期,反复点几次 GC 观察。如果每次都降不下来,基本确认泄漏。

4. 后续处理

JConsole 只能确认"发生泄漏",无法定位具体是哪些对象。需要用 jmap 导出堆文件,再用 Eclipse MAT 分析:

jmap -dump:format=b,file=heap.hprof <pid>

拿到 heap.hprof 后用 Eclipse MAT 打开,看 Dominator Tree 里谁占的内存最大、被谁引用,就能找到泄漏源头。

四、元空间(Metaspace)泄漏

常由动态类加载无限制引起(如 CGLIB 动态代理、大量 Groovy 脚本编译)。

排查步骤

在【内存】标签页,右下角选择"非堆内存"或"Metaspace"图表。如果它持续攀升且不降,就属于此类问题。

同样,确认后建议 dump 内存用工具分析类加载器。元空间泄漏的重点是找出谁在无限生成新类:CGLIB 代理、反射、脚本引擎每次都会产生新的 Class 对象,如果类加载器不被回收,这些类就永远留在元空间里。

总结操作习惯

连上 JConsole 后,按这个顺序快速过一遍:

  1. 看概览:整体印象,内存接近 100%?
  2. 看内存:点几下 GC,锯齿是否正常?
  3. 看线程:先查死锁,再看 BLOCKED 比例。
  4. 看 CPU:哪个用户线程在 RUNNABLE?

JConsole 更适合做实时确诊和初步定性,精确定位到代码行,通常还是需要配合 jstack/jmap 和日志。记住这套顺序,下次线上出问题,打开 JConsole 就能按图索骥。