应改用软引用或弱引用替代强引用缓存,并辅以主动淘汰策略、监控预警及慎用static缓存。软引用适合可丢弃但尽量保留的本地缓存,弱引用适合临时缓存;需设定容量上限、TTL/TTI,暴露指标告警,避免static强引用导致内存泄漏。

这个问题本质是缓存与 GC 协同失衡:强引用缓存锁死了对象生命周期,而 JVM 的 GC 触发时机(如 Eden 区满、老年代空间不足等)并不以“缓存是否该清”为依据,导致堆内存持续增长直至 OOM。
改用软引用或弱引用做缓存载体
软引用(SoftReference)在内存紧张时会被优先回收,适合做“可丢弃但尽量保留”的本地缓存;弱引用(WeakReference)在下一次 GC 就可能被回收,适合生命周期极短的临时缓存。
示例:
Map<String, SoftReference<User>> cache = new ConcurrentHashMap<>();
// 存
cache.put("u1001", new SoftReference<>(new User(...)));
// 取(注意判空)
SoftReference<User> ref = cache.get("u1001");
User user = ref != null ? ref.get() : null; // get() 返回 null 表示已被回收
增加主动淘汰策略,不依赖 GC 救场
本地缓存必须自带容量控制和过期机制,GC 只是兜底,不能当主力。
- 设定最大条目数(如 LRU 驱逐),用 LinkedHashMap 或 Caffeine 等成熟库
- 为每条缓存设 TTL(存活时间)或 TTI(空闲时间),避免长期驻留
- 避免缓存“永远不更新”的大对象(如全量用户列表),改用按需加载+细粒度缓存
监控与预警,把溢出拦在发生前
光靠逻辑不够,得让系统“看得见”缓存水位。
- 暴露缓存大小、命中率、回收次数等指标(如通过 Micrometer + Prometheus)
- 当缓存条目数 > 阈值 80% 或年轻代 GC 频次突增时告警
- JVM 启动参数中开启 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps,观察 GC 是否频繁 Full GC 却仍无法释放——这往往是强引用缓存卡死对象的信号
慎用 static Map 做全局缓存
static 引用默认是强引用,且生命周期与类加载器绑定,极易造成内存泄漏,尤其在热部署或模块卸载场景。
- 除非明确需要应用级常驻,否则避免直接 new HashMap 放 static 字段
- 若必须用,至少包装成 WeakHashMap(key 是弱引用),或配合显式清理逻辑
- 考虑用 Spring 的 @Cacheable + CaffeineCacheManager,它自动集成驱逐、统计、TTL


















