线上故障排查实战经验总结一

咱们的理论再完美,不如实际的上线考验,说再多、线上崩了也得会修。当生产环境出现 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 下载到本地,使用 VisualVMEclipse MAT (Memory Analyzer Tool 老工具了) 打开

分析重点

  1. Dominator Tree (支配树):查看哪个对象占用了最大的内存。
  2. Histogram (直方图):查看哪个类的实例数量最多。
  3. 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 进程,输入序号即可连接

总结:排查思路思维导图

当线上并发问题发生时,请按此流程操作:

  1. 现象确认:CPU 高?还是请求超时?还是 OOM?
  2. 定位进程top -> jps 拿到 PID。
  3. 分支判断
    • 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
      • 对策:修复内存泄漏、扩大堆内存。
相关推荐
勇闯逆流河1 小时前
【Linux】Linux基础开发工具(git、dbg)
linux·运维·服务器·开发语言·c++·git
填满你的记忆1 小时前
JVM 内存模型详解:Java 程序到底是如何运行的?
java·开发语言·jvm
RDCJM2 小时前
Plugin ‘org.springframework.bootspring-boot-maven-plugin‘ not found(已解决)
java·前端·maven
DJ斯特拉2 小时前
SpringBoot项目的基本构建
java·spring boot·后端
小小心愿家2 小时前
初识 maven,Spring boot,Spring MVC
java·后端·spring
身如柳絮随风扬2 小时前
Spring IOC容器的工作原理
java·spring
小温冲冲2 小时前
C++与QML交互指南:从基础到实战
开发语言·c++·交互
不会写DN2 小时前
Go中的泛型与any、interface有什么区别?
开发语言·后端·golang
yaoxin5211232 小时前
350. Java IO API - Java 文件操作:java.io.File 与 java.nio.file 功能对比 - 2
java·python·nio