Skip to content

Java 服务出现线程池耗尽、死锁或 CPU 飙高时怎么排查?

题型:问题排查 · 用多次线程快照和运行指标区分忙、等、堵和循环。

排查顺序

  1. 确认症状:关联业务 P99、超时率、CPU、Load、GC、线程数、线程池活跃数、队列长度和拒绝数,确认是单实例、单接口还是全局问题,并对照流量与发布变更。
  2. 线上止损:限流、熔断慢下游、隔离异常任务池或摘除故障实例。对结果未知的写请求不能盲目重试,要依赖幂等键、状态查询或对账确认副作用。
  3. 采集连续现场:间隔数秒获取多份 Thread Dump,必要时结合 JFR、async-profiler 和操作系统线程 CPU。单份快照只能说明瞬时状态,多份才能识别持续运行、持续阻塞或周期性竞争。
  4. 按线程状态定位BLOCKED 重点看锁持有者和竞争链;WAITING/TIMED_WAITING 结合队列、连接池和 Future 依赖判断是否正常;高 CPU 线程要把系统线程 ID 映射到 Java 栈,检查忙循环、重试风暴、正则回溯、序列化或频繁安全点。
  5. 检查资源依赖图:确认是否在线程池任务中同步等待同一个池里的子任务,是否持锁调用远程服务,是否存在锁顺序反转,以及连接池容量小于并发需求导致线程全部等待。
  6. 修复与验证:拆分线程池和 SLA,缩小锁范围,统一锁顺序,消除池内自等待,补充超时、取消和有界队列;通过故障注入和长稳压测验证队列可恢复、无任务丢失且尾延迟受控。
  7. 复盘监控:增加队列等待时间、最老任务年龄、拒绝数、锁等待、死锁检测、线程数和下游连接池告警,并保留可低成本触发的诊断采样。

可重试性判断

  • 还未执行的排队任务通常可以安全取消,但是否重试取决于业务幂等性和剩余时限。
  • 已发出远程写请求但本地超时属于结果未知,应先查询状态或对账。
  • 参数错误、权限错误等不可重试失败不应进入线程池重试风暴。

易错点

  • 线程多不等于死锁,WAITING 也不一定异常;必须结合等待对象、持续时间和业务指标。
  • CPU 高不一定是业务线程,也可能是 GC、JIT、本地库或内核开销。
  • 只重启能止损但会丢失现场,且不能解决无界队列、锁顺序或下游无超时等根因。

知识关系

从当前问题继续深入,或者回到提出这个问题的知识入口。