咱们的理论再完美,不如实际的上线考验,说再多、线上崩了也得会修。当生产环境出现 CPU 100% 、死锁 、线程阻塞 时,你需要的不是教科书,而是一套精准的手术刀。
就让我自作主张把 JVM 比作人体,线程比作细胞
- CPU 100% = 某个细胞在疯狂分裂(死循环/密集计算),导致全身发烧。
- 死锁 = 两个细胞互相掐住脖子打架,谁也不松手,导致局部组织坏死。
- 工具链 = CT 机(jstack)、核磁共振(jmap)、内窥镜(Arthas)
| 工具 | 别名 | 核心用途 | 场景比喻 |
|---|---|---|---|
top / htop |
听诊器 | 查看进程级 CPU/内存占用 | 发现哪个病人(进程)发烧了 |
jps |
挂号处 | 列出所有 Java 进程 ID (PID) | 确认病人的身份证号 |
jstack |
X 光/CT | 打印线程栈快照 | 看骨骼(线程)哪里断了,谁在打架(死锁) |
jmap |
抽血化验 | 导出堆内存快照 (Heap Dump) | 检查血液(对象)里有没有病毒(内存泄漏) |
jstat |
心电图 | 实时监控 GC 频率和内存变化 | 看心脏(GC)跳动是否过快 |
Arthas |
核磁共振+手术机器人 | 在线诊断、方法追踪、热更新 | 无创深入体内,实时看代码执行到哪一行 |
| VisualVM | 图形化体检中心 | 可视化分析线程、内存、CPU | 给非命令行爱好者看的详细报告 |
第一部分:实战演练一 ------ CPU 100% 排查 (谁在"发烧"?)
症状 :监控报警,某台服务器 CPU 飙升至 100%,系统响应极慢。
病因猜测:死循环、复杂计算、频繁 GC、或者大量线程在自旋。
自带工具
-
top -c
-
按P(按cpu使用率拍序)
-
找到java进程,记下pid(比如28456)
-
top -H -p 28456 (-H显示线程模式,-p指定进程ID)
-
再次按P(找到 CPU 占用最高的那个线程 ,记下它的 TID (例如
28499)) -
printf "%x\n" 28499(转换:
top的TID 是十进制 ,jstack输出的线程 ID 是十六进制) -
假设输出为
6f53。这就是我们要找的线索 -
jstack 28456 | grep -A 20 "0x6f53"(导出该进程的线程栈,并过滤出那个十六进制线程)
"pool-1-thread-5" #21 daemon prio=5 os_prio=0 tid=0x00007f8b4c006f53 nid=0x6f53 runnable [0x00007f8b3c5fe000]
java.lang.Thread.State: RUNNABLE
at com.example.service.CalcService.heavyLoop(CalcService.java:45)
at com.example.service.CalcService.process(CalcService.java:20)
... -
线程状态是
RUNNABLE(正在运行)。 -
代码卡在
CalcService.java的第 45 行,方法名heavyLoop。 -
行动 :赶紧去查第 45 行代码!是不是写了
while(true)没退出条件?是不是在搞一个巨大的正则匹配
Arthas 进阶大招 :
如果 jstack 看不出逻辑问题,直接用 Arthas 命令:
- thread -n 3 # 找出最忙的前 3 个线程,自动打印堆栈,无需手动转换进制!
- 或
trace命令监控方法耗时: trace com.example.service.CalcService process '#cost > 100' # 追踪耗时超过 100ms 的调用
第二部分:实战演练二 ------ 死锁定位 (谁在"打架"?)
症状 :系统突然不动了,CPU 不高,但请求全部超时。
病因猜测:线程 A 等 B 释放锁,B 等 A 释放锁,互相僵持。
-
jstack有一个隐藏技能,它能自动检测死锁并在报告末尾提示 -
jstack 28456
-
向下滚动到报告的最底部。如果有死锁,你会看到类似这样的明确提示:
Found one Java-level deadlock:
"Thread-1":
waiting to lock monitor 0x00007f8b4c001234 (object 0x000000076ab56780, a java.lang.Object),
which is held by "Thread-2"
"Thread-2":
waiting to lock monitor 0x00007f8b4c005678 (object 0x000000076ab12340, a java.lang.Object),
which is held by "Thread-1"Java stack information for the threads listed above:
"Thread-1":
at com.example.DeadLockDemo.methodA(DeadLockDemo.java:25)
- waiting to lock <0x000000076ab56780> (a java.lang.Object)
- locked <0x000000076ab12340> (a java.lang.Object)
at ...
"Thread-2":
at com.example.DeadLockDemo.methodB(DeadLockDemo.java:40)
- waiting to lock <0x000000076ab12340> (a java.lang.Object)
- locked <0x000000076ab56780> (a java.lang.Object)
at ... -
Thread-1 拿着锁
...12340,想要...56780。 -
Thread-2 拿着锁
...56780,想要...12340。 -
代码位置 :直接指向
DeadLockDemo.java的 25 行和 40 行。 -
行动:修复代码,统一锁获取顺序(例如都先拿 A 再拿 B)。
或者使用:thread -b # Arthas专门检测死锁 (blocker),一键输出死锁线程对
第三部分:实战演练三 ------ 内存泄漏与 OOM (谁在"吸毒"?)
症状 :OutOfMemoryError: Java heap space,或者 GC 频率极高(Full GC 停顿时间长)。
病因猜测:对象创建了没释放,堆积如山。
1、先看是不是 GC 的问题
- jstat -gcutil 28456 1000 10(每 1000ms 刷新一次,打印 10 次)
关注:FGC: Full GC 次数。如果疯狂增加,说明内存不够用了。关注:FGCT: Full GC 总时间。关注:O: Old Gen (老年代) 使用率。如果一直维持在 99% 且 FGC 后不下降,基本确认为内存泄漏
2、导出堆内存快照
-
生成 hprof 文件 (可能较大,需几秒到几分钟,期间服务会停顿)
- jmap -dump:format=b,file=heap_dump.hprof 28456
- 注意 :在生产环境执行此命令会导致 Stop-The-World (STW),业务会短暂中断。建议在流量低峰期或备用节点执行。
3: 分析快照 (VisualVM / MAT)
将 heap_dump.hprof 下载到本地,使用 VisualVM 或 Eclipse MAT (Memory Analyzer Tool 老工具了) 打开
分析重点:
- Dominator Tree (支配树):查看哪个对象占用了最大的内存。
- Histogram (直方图):查看哪个类的实例数量最多。
- GC Roots : 找到大对象的引用链,看是谁牢牢抓着它不让 GC 回收(通常是静态集合
static List或未关闭的资源)。
Arthas 进阶大招 (免 dump 文件) :如果不想 dump 大文件,可以用 Arthas 的 dashboard 看实时内存,或用 vmtool 直接查找对象:
vmtool --action getInstances --className com.example.User --limit 10
# 查看内存中现有的 User 对象,甚至可以直接查看它们的字段值
第四部分:Arthas 核心命令速查表 (神器中的神器)
| 命令 | 作用 | 示例 |
|---|---|---|
dashboard |
全局仪表盘 | 实时看 CPU、内存、GC、线程总数。相当于顶配版 top。 |
thread |
线程分析 | thread -n 3 (最忙线程); thread -b (死锁); thread <id> (看指定线程)。 |
trace |
方法调用追踪 | trace com.pkg.Class method '#cost>100' (追踪耗时>100ms 的调用路径)。 |
monitor |
方法监控 | monitor -c 5 com.pkg.Class method (每 5 秒统计一次 QPS/RT/失败率)。 |
watch |
观察参数/返回值 | watch com.pkg.Class method '{params, returnObj}' -x 3 (看入参和返回值,排查数据错误)。 |
tt |
时空隧道 | tt -t com.pkg.Class method (记录调用现场,稍后可回放 tt -i <index> -p 重放参数)。 |
heapdump |
在线 Dump | heapdump /tmp/dump.hprof (比 jmap 更友好,支持增量)。 |
Arthas 启动方式:很简单
java -jar arthas-boot.jar
# 或者
wget https://arthas.aliyun.com/arthas-boot.jar && java -jar arthas-boot.jar
它会列出所有 Java 进程,输入序号即可连接
总结:排查思路思维导图
当线上并发问题发生时,请按此流程操作:
- 现象确认:CPU 高?还是请求超时?还是 OOM?
- 定位进程 :
top->jps拿到 PID。 - 分支判断 :
- CPU 100% :
top -H -p找线程 ->printf转十六进制 ->jstack看堆栈。- 或直接
arthas thread -n 3。 - 对策:优化死循环、减少计算量。
- 请求卡顿/死锁 :
jstack PID看底部 "Found one Java-level deadlock"。- 或
arthas thread -b。 - 对策:调整锁顺序、减小锁粒度。
- 内存溢出/OOM :
jstat -gcutil看 GC 频率。jmap -dump导包 -> MAT 分析。- 或
arthas heapdump。 - 对策:修复内存泄漏、扩大堆内存。
- CPU 100% :