← 返回面试目录

问题排查思维链

看到一个现象,先想清楚它本质是什么,再顺藤摸瓜找到原因
每个现象都从"本质"切入,配一句话直觉,最后落到真实排查工具

这篇教你"怎么想",不是"怎么查"。
排查手册(03 篇)教你遇到问题用什么命令(top/jstack/jmap);这篇教你遇到问题时脑子里第一反应该想到什么 —— 先归类("这是忙死还是等死"),再顺藤摸瓜找原因。
面试时讲思路("CPU 高我第一反应是分单线程还是整体高,因为原因不同")比背命令链强 10 倍。

CPU 飙高

现象监控告警:服务器 CPU 使用率 90%+
响应变慢,甚至触发告警,服务可能雪崩。
本质:CPU 是计算单元,CPU 高 = 有东西在持续占用它做运算。要么是代码在死循环空转,要么是 GC 线程在拼命回收,要么是大量线程在抢锁自旋。
直觉:CPU 高,先分"是单线程高,还是整体都高" —— 单线程高大概率是代码问题,整体高大概率是 GC 或线程数问题。
🌊 顺藤摸瓜
① 定位进程
先确定是哪个进程 → top 找到 CPU 最高的 Java 进程 PID
top 看进程确认是 Java 进程
② 排除 GC
先排除最常见的 GC 问题 → jstat -gcutil PID 看 Full GC 次数 → 如果 FGC 一直涨,就是内存不够导致 GC 线程拼命回收抢 CPU → 转去查 OOM/内存
jstat -gcutilFGC 频率GC 线程占 CPU
③ 找线程
不是 GC,就找具体线程 → top -Hp PID 看哪个线程 CPU 最高 → 线程 ID 转十六进制 → jstack PID 搜 nid,看堆栈在执行什么代码
top -Hp 找线程线程 ID 转十六进制jstack 看堆栈
④ 单线程 100%
定位到单个线程 CPU 拉满 → 大概率代码问题 → 常见三种:① 死循环(while 没出口);② 正则回溯爆炸(用户输入特殊字符触发指数级匹配);③ 递归没终止条件
死循环正则回溯爆炸Catastrophic BacktrackingRE2 引擎
⑤ 整体偏高
不是单个线程,而是很多线程都偏高 → 可能是锁竞争激烈(大量线程自旋等锁空转)→ 或线程池配太大(线程太多互相抢占上下文切换)
锁竞争自旋线程池过大上下文切换
⑥ 流量激增
CPU 高 + 请求量暴涨 → 可能是正常业务高峰,也可能是被刷接口/CC 攻击 → 该限流了(Sentinel / Nginx limit_req)
限流(令牌桶)SentinelNginx limit_req
排查顺序: top 找进程 → 先 jstat 排 GC(最常见)→ 不是 GC 就 top -H + jstack 找线程 → 看堆栈定位到代码行。先排 GC 再排代码,因为 GC 导致的最多。

OOM(内存溢出)

现象java.lang.OutOfMemoryError: Java heap space
服务直接挂,重启或崩溃。
本质:JVM 要分配内存但堆里装不下了,GC 也回收不出足够空间。要么是瞬时塞了太多(大对象),要么是只进不出(泄漏)。
直觉:OOM 先分清"是突然爆(大对象)还是慢慢涨(泄漏)" —— 突然爆看代码哪里一次性加载大量数据,慢慢涨找引用链。
🌊 顺藤摸瓜
① 找大对象
内存装不下,先看是什么占的 → jmap -dump 导出堆快照 → MAT 打开看 Dominator Tree(支配树),按大小排序,第一个就是罪魁祸首
jmap -dumpMAT 分析Dominator Tree对象实例数
② 大对象?
MAT 显示某个对象特别大/特别多 → 如果是瞬时撑爆 → 常见:① 一次性查 50 万行(没分页);② 导出大 Excel;③ 大文件全量读入 → 改成分页/流式处理
查询没分页EasyExcel 流式游标查询异步导出
③ 泄漏?
如果是只增不减(对象数量持续涨)→ 泄漏 → 根因:对象没法回收 = GC 还能从某个 GC Root 找到它 = 有人还引用着 → 找 GC Root 引用链(MAT 的 Path to GC Roots)
内存泄漏GC Root 引用链可达性分析Path to GC Roots
④ 常见泄漏源
经典几种:① ThreadLocal 没 remove(线程池复用线程,key 弱引用会回收但 value 强引用不回收);② 静态集合当缓存只 add 不 remove;③ 监听器/定时器没注销;④ 连接没 close
ThreadLocal 没 remove静态 Map 增长监听未注销连接未关闭
⑤ 其他 OOM
不是 heap space?看具体报错:Metaspace(动态代理生成类太多)/ Direct memory(NIO 直接内存)/ unable to create thread(线程数到系统上限)/ StackOverflow(递归太深)
MetaspaceCGLIB 动态代理Direct memory线程数过多
⑥ OOM 前兆
OOM 之前 GC 会拼命救场 → 所以 OOM 前往往先 CPU 高(频繁 Full GC),再彻底放弃抛 OOM → 配 HeapDumpOnOutOfMemoryError 自动留现场
先 CPU 高后 OOMFull GC 救场失败自动 dump 参数
三大原因:大对象(瞬时撑爆)/ 泄漏(慢涨不清)/ 堆太小(参数问题)。先分清是哪种 —— 大对象改分页,泄漏找引用链,堆太小调 Xmx。

GC 太频繁

现象接口周期性卡顿,Full GC 频繁
RT 抖动,监控看 FGC 曲线密集。
本质:GC 清垃圾腾空间,频繁 = 垃圾产生太快 / 回收效率低 / 空间太小。每次 GC 要暂停应用(STW),用户就感觉卡。
直觉:GC 频繁先问三个问题:堆够不够大?对象产生快不快?有没有泄漏回收不掉?
🌊 顺藤摸瓜
① 看 GC 频率
jstat -gcutil PID 看 YGC(年轻代)和 FGC(Full GC)次数 → 哪个涨得快就是哪个的问题
jstat -gcutilYGC vs FGC
② YGC 频繁
年轻代 GC 频繁 = 对象产生太快 → 看代码哪里狂 new:① 循环内 new 对象;② 大 JSON 反序列化;③ 日志对象;④ 自动装箱(Integer.valueOf)
循环内创建JSON 反序列化自动装箱对象池复用
③ FGC 频繁
Full GC 频繁 = 老年代不够 → 为什么老年代满?① 大对象直接进老年代;② Survivor 太小对象提前晋升;③ 泄漏老年代对象不回收
大对象进老年代Survivor 太小老年代泄漏
④ STW 停顿长
单次 GC 停顿久 = 用户卡 → 可能收集器不对 → 换 G1(分 Region,可预测停顿)/ ZGC(亚毫秒停顿) → 或堆太大单次回收久
STW 停顿G1 可预测停顿ZGC 低延迟吞吐 vs 延迟
堆够不够大?对象产生快不快?有没有泄漏?前两个调参数/优化代码,第三个要 jmap 找泄漏对象。

接口慢

现象接口 RT 从 100ms 涨到 3 秒
用户体验差,可能超时。
本质:接口慢 = 某个环节耗时长。请求要经过网络→应用→DB→下游,哪段慢整体就慢。关键是先定位卡在哪一段。
直觉:别瞎优化,先用 APM 看链路,找最长的那个段下钻。
🌊 顺藤摸瓜
① 看链路
APM 链路追踪(SkyWalking/Pinpoint)看每段耗时 → traceId 串联整个调用链 → 找最长的段
APM 链路追踪SkyWalkingtraceId 串联
② DB 段慢
DB 段最长 → SQL 问题 → 慢查询日志 + EXPLAIN → 常见:① 没索引/索引失效;② N+1 查询(循环里查 DB);③ 大表没分页;④ 锁等待
EXPLAIN索引失效N+1 查询大分页
③ 应用段慢
应用段最长 → 代码问题 → 常见:① 串行调多接口(改并行 CompletableFuture);② 同步阻塞(IO 等待);③ 大循环计算;④ JSON 序列化大对象
串行→并行CompletableFuture异步化
④ GC/锁
应用慢 + CPU 也高 → 可能 GC STW 停顿 → 或线程都 BLOCKED 等锁jstack 看线程状态分布
GC STW锁等待 BLOCKEDjstack 状态
⑤ 下游慢
下游段最长 → 第三方/其他服务慢 → 必须设超时(别无限等)+ 熔断(挂了快速失败)+ 降级(返回兜底)
超时设置熔断 Sentinel降级兜底
先 APM 找瓶颈段,不要瞎猜。DB 慢查 SQL 索引,应用慢查代码/GC/锁,下游慢加超时熔断。

死锁 / 接口卡死

现象接口偶发卡死超时,请求越堆越多
线程数飙升,但 CPU 不高(都在等)。
本质:两个或多个线程互相等对方手里的锁,谁都不放手,全部卡住。特征:CPU 不高(都在等待不消耗 CPU),但请求全超时。
直觉:请求卡住 + CPU 不高 = 线程都在"等"(不是在"算")→ 大概率是锁或连接池耗尽。
🌊 顺藤摸瓜
① 看线程状态
jstack PID 看线程状态 → 大量 BLOCKED 或 WAITING → 搜 "Found one Java-level deadlock" 自动检测
jstack 线程 dumpBLOCKED 状态死锁自动检测
② 经典场景
两个事务相反顺序加锁:A 先锁记录 1 再锁 2,B 反过来 → 互相等 → 死锁
MySQL 看 SHOW ENGINE INNODB STATUS 的 LATEST DETECTED DEADLOCK
加锁顺序不一致SHOW ENGINE INNODB STATUS死锁四条件
③ 预防
统一加锁顺序(所有事务按同样顺序锁);② 缩短事务(持锁时间短);③ tryLock(timeout)(等不到就放弃不死等)
统一加锁顺序缩短事务tryLock 超时
④ 类似但要区分
连接池耗尽(都在等连接)、活锁(互相谦让都在动但没用)、饥饿(某线程一直抢不到锁)→ 都表现为"卡住",jstack 看状态区分
连接池耗尽活锁饥饿
和 CPU 高正相反:一个是"忙死"(死循环烧 CPU),一个是"等死"(死锁不耗 CPU 但全卡住)。看 CPU 高低就能区分。

磁盘满

现象No space left on device
日志写不了,数据库插不进,各种报错。
本质:存储空间用完了。日志、Docker 镜像、GC dump、数据库 binlog 是四大杀手。
直觉:先找谁占的(df/du),再分清是垃圾(可删)还是有用数据(要扩容),最后注意"删了没释放"说明有进程还在写。
🌊 顺藤摸瓜
① 定位
df -h 看哪个分区满 → du -sh * 逐层找大目录 → find -size +100M 找大文件
df -h 分区du -sh 逐层find 大文件
② 常见元凶
日志没配滚动(一个文件写几十 G);② Docker 旧镜像堆积;③ GC dump 文件;④ MySQL binlog 没配过期清理
日志滚动 logbackdocker prunebinlog 过期
③ 删了没释放?
删了文件空间没释放 → 有进程还在写(文件删了但 fd 没关)→ lsof | grep deleted 找到 → 重启对应进程
fd 未关闭lsof grep deleted重启释放
④ 预防
日志配滚动策略 + 定时清理脚本(crontab) + 磁盘 80% 告警
滚动策略crontab 清理磁盘告警

缓存击穿 / DB 被打挂

现象缓存失效瞬间,DB 连接数飙升甚至挂了
某个热点数据缓存过期,大量请求直奔 DB。
本质:缓存是挡在 DB 前面的缓冲层。缓存失效了,所有请求直接打到 DB,DB 扛不住就崩。
直觉:DB 被打挂先想缓存出了什么问题 —— 穿透(查不存在的)、击穿(热点过期)、雪崩(批量过期),三种情况解法完全不同。
🌊 顺藤摸瓜
① 穿透
查压根不存在的数据 → 缓存没有 DB 也没有 → 每次都打 DB(可能是恶意攻击查 id=-1)→ 解法:缓存空值(null 也缓存短时间)/ 布隆过滤器(提前判断可能不存在)
缓存空值布隆过滤器参数校验
② 击穿
热点 key 过期瞬间 → 几万请求同时发现缓存没了 → 同时查 DB → 解法:互斥锁(只放一个查 DB,其他等)/ 热点 key 永不过期(逻辑过期异步刷新)
互斥锁热点永不过期异步刷新
③ 雪崩
大量 key 同时过期(批量预热过期时间一样)→ 同时失效全打 DB → 解法:过期时间加随机(打散)/ 多级缓存 / 限流保护 DB
过期加随机多级缓存限流保护
④ 一致性
延伸:更新数据时缓存怎么办 → Cache Aside(先更 DB 再删缓存)+ 延迟双删兜底 → 强一致别用缓存(只能最终一致)
Cache Aside延迟双删最终一致

消息积压

现象MQ 消息堆积越来越多
生产速度超过消费速度,业务延迟。
本质:MQ 是生产者和消费者之间的缓冲。积压 = 消费跟不上生产。要么消费者不够,要么消费太慢,要么消费者挂了。
直觉:先看消费速率 —— 还在消费但慢 = 处理能力不足;消费速率为零 = 消费者挂了。两种情况处理方式完全不同。
🌊 顺藤摸瓜
① 看积压
Lag(积压量) + 消费速率 → 还在消费但慢 = 处理能力不足;速率为零 = 消费者挂了
Lag 监控消费速率kafka-consumer-groups
② 消费者挂了
看日志 → 常见:① 毒消息一直失败卡住(丢死信队列);② OOM;③ 依赖的 DB/Redis 挂了
毒消息死信队列 DLQ重试限制
③ 消费慢
单条处理久 → ① 同步调下游(改异步);② DB 操作慢(批量写);③ 并发消费(多线程/多消费者)
批量处理并发消费增加 partition
④ 紧急处理
加消费者实例扩容 / 增加 partition / 临时跳过非核心消息 / 恢复时限流(别打爆下游)
扩容消费者增加 partition消费限流
注意:积压恢复时消费速率可能突然飙升,瞬间打爆下游 DB!务必加消费限流,宁可多积压一会,别引发更大故障。

总结

现象本质第一反应排查方向
CPU 高计算单元被持续占用单线程高(代码)还是整体高(GC/锁)jstat 排 GC → top -H + jstack 排代码
OOM堆分配不下了突然爆(大对象)还是慢涨(泄漏)jmap + MAT 找大对象/引用链
GC 频繁垃圾产生太快/回收不掉堆够不够?对象多不多?有泄漏吗?jstat → 调堆/优化/找泄漏
接口慢某段耗时长卡在哪一段?APM 定位瓶颈段
死锁互相等锁全卡住卡住 + CPU 不高 = 在等不是在算jstack / SHOW ENGINE INNODB STATUS
磁盘满空间耗尽谁占的?垃圾还是有用?df → du → lsof
缓存击穿缓存失效 DB 受冲击穿透/击穿/雪崩?空值/互斥锁/随机过期
消息积压消费跟不上生产消费者挂了还是慢?扩容/批量/限流/死信
核心思路:先搞清楚现象的本质是什么(CPU 高 = 计算被占用、OOM = 内存不够、慢 = 某段堵了),归类对了方向就对了,剩下的就是用工具验证。面试时讲思路,别背命令。
配套:这篇教你"怎么想",《运维问题排查实战》 教你"怎么查"。先想清楚方向(这篇),再用命令验证(那篇)。