缓存击穿是单个热点key过期瞬间并发查询数据库,雪崩是大量key集中失效或redis宕机导致请求全压数据库;二者均非内存泄漏,而是瞬时流量冲击引发的内存暴涨,可通过监控miss率、db qps及堆内存陡升特征快速区分。

缓存击穿和缓存雪崩本身不是内存泄漏的直接原因,而是高并发场景下的业务逻辑问题,它们可能诱发内存暴涨甚至OOM,但机制与内存泄漏不同——前者是瞬时流量冲击导致对象创建激增或线程堆积,后者是对象长期无法被GC回收。排查时需先区分现象本质,再针对性切入。
看清现象:是“爆”还是“漏”?
-
缓存击穿/雪崩引发的内存暴涨:
- 表现为短时间(秒级到分钟级)堆内存陡升、YGC频率飙升、大量临时对象(如DTO、JSON解析结果、SQL参数)涌入新生代;
- Full GC后内存能明显回落,服务恢复后增长趋势中断;
- 日志中常见大量超时、降级、熔断记录,而非持续缓慢爬升的堆曲线。
-
真正的内存泄漏:
- 堆内存呈阶梯式或线性持续上升,Full GC后老年代占用不下降;
- 重启后几小时内复现相同增长节奏;
- 和缓存是否击穿无强关联,即使关闭缓存、压测低流量仍会缓慢上涨。
✅ 简单判断:同一业务请求下,加缓存 vs 不加缓存,内存是否都持续上涨?如果只在缓存失效窗口暴涨且能回落,大概率是雪崩/击穿带来的瞬时压力,不是泄漏。
查缓存失效引发的内存异常增长
1. 监控关键指标,定位爆发源头
- 用
jstat -gc <pid> 1000</pid>观察:-
S0C/S1C是否快速耗尽 → 新生代太小或对象晋升过快; -
EC(Eden区)使用率是否反复打满 → 大量短生命周期对象生成; -
YGC次数是否从每分钟几次飙到每秒多次 → 典型雪崩信号。
-
- 结合业务监控看:
- 缓存 Miss 率是否突增至95%+;
- DB QPS是否同步翻倍;
- 接口平均响应时间是否从20ms跳到800ms+。
2. 抓取“爆发中”的堆快照,聚焦临时对象
- 在缓存大面积失效、内存刚冲高的时刻执行:
jmap -dump:format=b,file=heap-burst.hprof <pid></pid>
- 用 MAT 打开,按 "Group by package" → 查看
com.xxx.dto、com.fasterxml.jackson、org.springframework.web等包下的实例数; - 若发现
HashMap,ArrayList,String,byte[]实例数异常多(比如百万级),且多数属于你自己的VO/DTO类 → 说明是业务对象爆炸式创建未及时释放,非静态持有。
3. 检查缓存层设计是否放大压力
常见加剧内存压力的写法:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- ❌ 使用
new HashMap()包装缓存结果,每次查询都新建对象; - ❌ JSON反序列化未复用
ObjectMapper,或启用了DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY等高开销特性; - ❌ 缓存空对象(null)未设短TTL,导致穿透后大量重复加载失败;
- ❌ 本地缓存(如 Caffeine)配置了
maximumSize = Long.MAX_VALUE且未设 expire,实际变成内存黑洞。
✅ 改进方向:
- 对高频DTO类启用对象池(如 Apache Commons Pool 或自定义轻量池);
-
ObjectMapper全局单例 + 配置configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true)减少包装类; - 空结果缓存统一设
expireAfterWrite(2, TimeUnit.SECONDS); - 本地缓存必须同时设
maximumSize(10000)和expireAfterWrite(10, TimeUnit.MINUTES)。
区分清楚后,该防泄漏还是防雪崩?
-
如果确认是缓存雪崩/击穿引起内存暴涨:
- 加分布式锁(如 Redis SETNX)保护缓存重建;
- 设置多级缓存(本地 + 分布式),本地加随机过期时间防共振;
- 启用熔断(Sentinel/Hystrix)快速失败,避免线程池打满;
- JVM调优:适当增大
-Xmn,避免 Eden 区过小导致频繁 YGC。
-
如果暴涨之后内存不回落、持续攀升:
- 回头重点查
static Map、ThreadLocal<map></map>、未注销的监听器、线程池任务队列堆积; - 用
jmap -histo <pid> | head -20</pid>看是否有你自己的类长期霸榜; - MAT 中打开 "Dominator Tree",找谁在“死死拽着”那些 DTO 对象 —— 很可能是某个静态缓存容器或 Filter 中未清理的 ThreadLocal。
- 回头重点查
不复杂但容易忽略
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










