生产环境 CPU 使用率 90%+:原因 + 排查 + 解决方案
目录
一、常见根因分类(按出现概率排序)
(一)应用代码层面(最常见)
(二)中间件 & 框架层面
(三)系统 & 运维层面
(四)网络 & 外部依赖
二、标准排查流程(Linux 环境,从上到下定位)
1. 全局看整机 CPU
2. 定位进程内高 CPU 线程
3. 分析 Java 应用(绝大多数后端场景)
(1)查看线程堆栈,定位死循环 / 死锁
(2)排查 GC 问题(高频 CPU 元凶)
(3)分析 CPU 热点方法(定位低效代码)
4. 排查非 Java 进程
三、分场景解决方案
场景 1:代码死循环 / 无限递归
场景 2:GC 频繁、内存泄漏(最常见)
场景 3:慢 SQL 导致 CPU 高
场景 4:锁竞争、自旋、上下文切换高
场景 5:日志量过大
场景 6:定时任务扎堆
场景 7:IO 阻塞 + 重试抬升 CPU
场景 8:线程数量过多
四、线上应急快速操作(紧急降 CPU)
五、事前预防(常态化)
一、常见根因分类(按出现概率排序)
(一)应用代码层面(最常见)
- 死循环 / 无限递归循环无终止条件、递归无出口,线程持续占用 CPU。
- 频繁 GC(垃圾回收)内存泄漏、对象创建销毁极频繁、大对象 / 短生命周期对象爆炸,Full GC / Young GC 频繁,GC 线程抢占 CPU。
- 低效代码 & 密集计算大量循环遍历、嵌套循环、复杂正则、频繁序列化 / 反序列化、大数据量内存计算。
- 锁竞争激烈大量自旋锁、synchronized/ReentrantLock 高并发争抢,线程空转消耗 CPU。
- 线程泛滥无界线程池、手动疯狂创建线程,线程上下文切换(上下文切换开销)飙升。
- 死锁 / 活锁活锁线程反复重试空跑;死锁伴随线程阻塞,但外围重试逻辑仍会拉高 CPU。
(二)中间件 & 框架层面
- 数据库问题慢 SQL、全表扫描、缺失索引、笛卡尔积、未分页查询大量数据,应用反复轮询 / 重试。
- 消息队列消费速度远大于处理能力,消费线程空转、反复拉取消息。
- 缓存问题缓存穿透 / 击穿 / 雪崩,大量请求直达 DB + 重试逻辑叠加。
- 框架 bug / 不合理配置定时任务扎堆执行、心跳 / 巡检任务频率过高、第三方 SDK 死循环。
(三)系统 & 运维层面
- 进程 / 脚本异常恶意脚本、定时任务(shell / 脚本)批量跑批、日志狂打。
- IO 阻塞引发的 CPU 升高磁盘 IO、网络 IO 阻塞后,业务线程不断重试、轮询,间接拉高 CPU。
- 系统配置不合理服务器核数少、应用进程 CPU 配额限制过小、虚拟化 / 容器资源争抢。
- 日志爆炸日志级别设为
DEBUG、海量日志打印、日志同步 / 滚动占用 CPU。
(四)网络 & 外部依赖
接口超时、第三方服务响应慢,应用大量重试、超时轮询、熔断重试逻辑失控。
二、标准排查流程(Linux 环境,从上到下定位)
1. 全局看整机 CPU
top # 实时查看整机CPU、各进程占用 Shift+P # top 内按CPU排序,找到高CPU PID- 观察:us (用户态)、sy (内核态)、wa (IO 等待)、% id (空闲)
us 极高:应用代码 / GC 问题(主流场景)sy 极高:线程上下文切换、内核态频繁调用、系统调用过多wa 极高:IO 瓶颈,IO 阻塞导致业务重试抬升 CPU
2. 定位进程内高 CPU 线程
拿到高 CPU 进程 PID(假设为1234):
# 导出该进程所有线程,按CPU排序 top -Hp 1234 # 将线程PID转为16进制(后续jstack使用) printf "%x\n 线程PID3. 分析 Java 应用(绝大多数后端场景)
(1)查看线程堆栈,定位死循环 / 死锁
jstack 1234 > thread.log # 搜索16进制线程ID,找到卡死/循环的线程栈- 特征:同一行代码反复出现 →死循环 / 低效代码
- 特征:提示
Deadlock→ 死锁
(2)排查 GC 问题(高频 CPU 元凶)
# 实时查看GC状态 jstat -gc 1234 1000 # 查看GC概览 jmap -heap 1234 # 导出堆快照(生产建议低峰执行,避免STW) jmap -dump:format=b,file=heap.hprof 1234判断:
- YGC/FGC 频率极高、GC 耗时久 → 内存问题
- 堆内存被占满、老年代持续上涨 →内存泄漏
(3)分析 CPU 热点方法(定位低效代码)
使用arthas(线上首选,无侵入):
# 监控热点函数、CPU占比 thread -n 5 # 展示Top5高CPU线程 profiler start # 采样CPU热点 profiler stop4. 排查非 Java 进程
- 脚本 / Go/Python 进程:
strace追踪系统调用,看是否死循环、频繁轮询 - 数据库 / 中间件:进入对应组件日志,查看慢查询、连接数、队列堆积
三、分场景解决方案
场景 1:代码死循环 / 无限递归
- 根据
jstack/arthas定位循环代码,补充循环终止条件、边界判断。 - 递归改为迭代,设置递归深度上限。
场景 2:GC 频繁、内存泄漏(最常见)
- 临时缓解:重启应用(应急)。
- 根治:
- 修复内存泄漏:排查静态集合、未关闭连接(DB/Redis/ 文件流)、全局缓存溢出。
- 优化对象创建:避免循环内创建对象、复用对象(池化)。
- 调整 JVM 参数:合理设置堆大小、新生代比例,开启 G1/ZGC 等低延迟 GC。
- 禁止循环创建大量临时对象。
场景 3:慢 SQL 导致 CPU 高
- 开启慢查询日志,用
explain分析执行计划。 - 加索引、优化联表、分页、避免全表扫描。
- 热点数据加本地缓存 / Redis,减少 DB 访问。
场景 4:锁竞争、自旋、上下文切换高
- 缩小锁粒度、降低锁持有时间。
- 替换重量级锁为乐观锁、分段锁。
- 优化线程池:使用有界队列、合理核心 / 最大线程数,禁止无界线程池。
场景 5:日志量过大
- 线上统一把日志级别改为
INFO/WARN,关闭DEBUG。 - 异步日志(logback/log4j2 开启异步)、精简日志内容。
- 限制大报文打印、避免循环内打日志。
场景 6:定时任务扎堆
- 错峰执行定时任务,避免同一时间大量任务启动。
- 降低巡检、心跳类任务执行频率。
场景 7:IO 阻塞 + 重试抬升 CPU
- 优化磁盘 / 网络 IO,升级硬件、拆分大文件。
- 接口 / DB 访问增加熔断、限流、退避重试(指数退避,禁止无限重试)。
场景 8:线程数量过多
- 统一使用业务线程池,全局管控线程数量。
- 限制连接池(DB、Redis、MQ)最大连接数。
四、线上应急快速操作(紧急降 CPU)
- 优先扩容节点 / 集群,分流压力。
- 临时关闭非核心定时任务、非核心接口。
- 关闭 DEBUG 日志、清理堆积队列。
- 确认是 GC 问题 →分批重启实例(业务无影响前提下)。
- 临时限流、降级非核心功能。
五、事前预防(常态化)
- 监控:接入 CPU、GC、线程数、慢 SQL、接口耗时告警。
- 压测:上线前做高并发压测,提前暴露死循环、低效代码。
- 规范:统一线程池、锁、日志、JVM 参数规范。
- 限流降级:核心接口标配限流、熔断、重试策略。
