一、内存相关(最常见、最致命)
1. 内存泄漏(Memory Leak)
- 现象 :堆内存只涨不跌,Full GC 后不下降,最终 OOM
- 根本原因:对象被长期持有(static 集合、ThreadLocal、线程池、连接未释放)
- 后果:服务卡死、重启才能恢复
- 判断方法 :
- jstat -gc 看到:老年代持续上涨,Full GC 后内存不下降
- 监控曲线:堆内存只增不减
- 最终抛出:
OOM: Java heap space
- 定位泄露代码:
- 导出堆快照:# 自动导出(推荐,OOM 时自动 dump,不影响运行) -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heap.hprof
- MAT分析
- 看引用链
- 解决方法
- 打断引用链:手动设置null、调用 remove/clear/close
- 加自动清理机制:过期时间、最大容量、淘汰策略
- 使用安全工具类:try-with-resources、规范线程池
- 截止无界缓存、无界集合
- 应急方法
- 重启服务
- dump命令输出内存信息
- 修复、上线
- 预防机制
- 代码规范:ThreadLocal 必 remove ,连接必关闭
- 禁止使用 static 集合做本地缓存
- 缓存必须用:Caffeine、Guava、Redis
- 开启监控:堆内存、Full GC 次数
- 代码 review 重点检查:缓存、线程池、IO
2. 内存溢出 OOM
- 根本原因 :内存泄漏、大对象、堆设置太小
- 常见的OOM
- 堆溢出:
Java heap space - GC 超时溢出 GC overhead limit exceeded
- 堆溢出:
解决方法- 禁止一次性加载大量数据:分页、分批、流式处理
- 禁止无界缓存:不用 static Map 做缓存
- 资源必须关闭
合理使用线程池
1.2.1. 堆内存溢出
-
现象:报错
java.lang.OutOfMemoryError: Java heap space -
原因
-
一次查询全表数据(几十万、上百万条)
-
内存泄漏耗光堆
-
堆内存设置太小
-
大对象(大 List、大文件、大报文)
-
-
解决方法
- 临时扩容(快速恢复)
- 代码根治
- SQL 必须分页查询 ,禁止
select *全表加载 - 修复内存泄漏(static 集合、ThreadLocal 等)
- 大文件流式处理,不要一次性读入内存
- SQL 必须分页查询 ,禁止
- 检查是否有超大对象、超大集合
- 元空间溢出:
Metaspace - 直接内存溢出
- 元空间溢出:
1.2.2. GC 超时溢出
-
现象:GC 占用太多 CPU,回收不掉内存,直接抛 OOM
-
原因:
-
内存快耗尽,Full GC 疯狂跑
-
99% 是内存泄漏
-
-
解决方法:
- 按内存泄漏流程排查
- 清理无用对象引用
- 适当加大堆内存
3. GC 频繁 / Full GC 频繁(几分钟一次,甚至几十秒一次)
-
现象:CPU 飙升、接口响应慢、TP99 暴涨
-
根本原因:内存泄漏、新生代太小、大对象直接进入老年代
-
判断方法 :jps # 找到进程 PID 、jstat -gc PID 1s # 实时看 GC
- YGC 飙升 → 新生代问题
- FGC 飙升 + 老年代涨 → 内存泄漏 / 堆太小
- FGC 后内存不下降 → 100% 内存泄漏
-
解决方法:
-
急救(5 分钟恢复):重启服务、临时加大堆内存
-
加 JVM 参数:
- YGC 频繁 → 加大 -Xmn
- FGC 频繁 → 加大堆 Xmx,或使用 G1
- FGC 频繁 + 内存不下降 → 内存泄漏,必须改代码
- 高并发接口 → 新生代给大一点(1/2 堆)
- 业务对象多、缓存多 → 老年代给大一点
-
定位根因
-
jstat 看 FGC 是否频繁
-
有泄漏 → MAT 分析
-
无泄漏 → 调大内存 + 优化代码
-
-
-
代码根治:
- 分页查询,禁止全表加载
- ThreadLocal 必须 remove
- 连接 / 流必须关闭
- static 集合不能无限增长
- 线程池必须用有界队列
二、CPU 相关(极易触发告警)
1. CPU 使用率过高(100%/80%+)
- Top 原因及解决方法 :
-
Full GC 疯狂执行(占 70%):通过jstat -gc PID xxx 命令确认。 如果 FGC 疯狂上涨、秒秒都在 GC → CPU 高 = GC 导致
如果 GC 正常、YGC/FGC 都不高 → CPU 高 = 业务代码导致
-
代码死循环:找到代码,加结束条件
-
大量线程锁竞争:减少锁粒度、使用无锁结构、分段锁
-
复杂计算 / 正则 / 序列化:优化正则、拆分、限制长度
-
- 后果:服务不响应、雪崩
2.1.1. GC 导致 CPU 高(最常见)
- 现象
- jstat 看到 FGC 频繁上涨
- GC 线程把 CPU 打满
- 解决方法(直接做)
- 加大堆内存
- 按之前的 Full GC 频繁方案处理
- 排查内存泄漏(导出 dump,MAT 分析)
2.1.2. 业务代码导致 CPU 高(真正要查代码)
- 排查方法:
- 找到耗 CPU 的 Java 进程 ------ top命令找到PID
- 找到进程内最耗 CPU 的线程 ------ top -Hp PID拿到线程TID
- 把线程 ID 转成 16 进制 ------ printf "%x\n" TID
- 导出线程栈,定位代码行 ------ jstack 16进制结果 > cpu.log
2. 死锁 / 锁竞争激烈
- 现象:接口卡住、线程大量 WAITING
- 根因:synchronized 嵌套、ReentrantLock 未释放、线程池设计不合理
- 定位:
- 直接查死锁(自动检测)
- jstack -l PID > lock.log
- 打开日志,搜索关键字 Found 1 deadlock
- 看锁竞争:
- jstack PID | grep -C 10 "WAITING"
- 大量线程 WAITING 且都等同一个锁地址→ 锁竞争激烈
- 直接查死锁(自动检测)
- 解决方法:
- 统一锁的获取顺序
- 使用定时锁,避免无限等待
- 减少嵌套锁(不要锁中锁)
- 无锁设计(ThreadLocal、CAS)
- 加锁粒度拆小
| 低效锁 | 推荐高效锁 |
|---|---|
| synchronized 全局锁 | ReentrantLock |
| 普通 HashMap | ConcurrentHashMap |
| 自己写的锁 | LongAdder(计数) |
| 独占锁 | 读写锁 ReadWriteLock |
三、接口 / 业务性能(用户感知最强)
1. 接口响应慢 / TP99 飙升
- 最常见原因 :
- 慢 SQL(没索引、连表太深)
- 外部接口调用超时
- Redis 缓存失效 / 击穿
- 锁等待、GC 停顿
- 大事务、长事务
- 定位:
- 看监控(链路追踪):Arthas看 **哪一段耗时最高?**是 DB?HTTP?Redis?还是业务代码?
- 抓慢接口日志(无监控也能查):grep -r "接口名" app.log | grep "耗时";偶尔慢 (GC / 锁),还是一直慢(SQL / 外部调用)
- Arthas 一键定位:
-
查看方法耗时 ------ trace 类名 方法名
-
查看慢调用 ------ watch 类名 方法名 '{params,returnObj,throwExp}' -n 5 -x 3 'cost>100'
-
3.1.1. 慢 SQL / DB 慢(最常见)
- 现象:
- 接口 TP99 突然飙升
- 数据库 CPU 高、连接池等待
- 日志里 SQL 执行几十~几百毫秒
- 解决方法:加索引、分页查询、大SQL拆分、加缓存
3.1.2. 外部接口调用慢(HTTP/Dubbo)
- 现象:
- 调用第三方服务、支付、短信、其他微服务
- 超时、响应慢、不稳定
- 解决方法:加超时时间、熔断降级、异步化、加缓存
3.1.3. Redis 问题
- 现象:
- 缓存击穿、缓存雪崩
- Redis 命令慢
- 高并发下缓存失效,请求打到 DB
- 解决方法:
- 缓存穿透 / 击穿:加布隆过滤器、空值缓存
- 缓存雪崩:过期时间加随机值、集群部署
- 大 Key 优化:避免超大 Hash、List
2. 线程池耗尽 / 异步任务堆积
- 因果关系:
- 任务堆积 → 处理不过来
- 队列满了 + 最大线程数也满了 → 线程池耗尽
- 报错 :
RejectedExecutionException
- 根因:线程池参数不合理、任务阻塞、消费不过来
- 直观判断:
- 日志大量抛:拒绝执行异常
- 异步任务不执行、延迟极高
- 接口被拖慢、雪崩
- 定位:arthas-boot.jar threadpool看到 活动线程数、队列堆积数、任务执行耗时
- 解决方法
- 调整队列大小:建议200~500 根据并发调整
- 优化线程池参数:
- CPU 密集型 (计算):最大线程 = CPU 核数 + 1
- IO 密集型 (HTTP、DB、Redis):最大线程 = 50~200
- 更换拒绝策略:推荐:CallerRunsPolicy
- 队列满了 → 让调用者自己执行
- 不会丢任务、不会雪崩
- 相当于自动限流
- 优化任务执行速度:
- SQL 慢 → 加索引
- HTTP 调用慢 → 加超时、熔断
- 锁等待 → 缩粒度、换无锁
- 外部调用慢 → 异步化、并行化
- 终极解决方案(高并发):异步任务太多 → 用 MQ 削峰 (最稳)
- 任务不直接扔线程池
- 发送到 RabbitMQ / RocketMQ / Kafka
- 消费者慢慢消费彻底解决堆积、耗尽、雪崩
四、连接 / 资源泄漏(隐形杀手)
1. 连接泄漏(DB/Redis/HTTP)
- 现象:运行一段时间后无法获取连接
- 根因:连接获取后未关闭、try-with-resources (Java 7+ 专门用来解决资源泄漏)未使用
2. 文件句柄 / 流未关闭
- 现象:导致 Too many open files
- 结果:服务崩溃无法重启
- 解决:使用 try-with-resources 所有流、文件、连接自动关闭