缓存“物理泄漏”本质是设计层面资源滞留,表现为无ttl、无容量限制、无清理机制的纯内存缓存持续单向占用内存;须强制配置ttl+最大容量、禁用永不过期、收敛key空间并引入弱引用兜底。

系统缓存框架退化为“物理泄漏”,本质是缓存数据长期驻留、无法释放,最终等效于内存被永久占用——这不是GC能回收的对象泄漏,而是设计层面的资源滞留。它不依赖对象引用未断,而源于缓存策略缺失或失效,导致数据在内存中无限堆积、不可驱逐。
关键判断:是不是真“物理泄漏”?
先确认是否满足以下全部特征:
- 缓存使用的是纯内存结构(如
HashMap、sync.Map、未配LRU的gcache.New()) - 未设置任何过期时间(TTL = 0 或
DurationNoExpire) - 无容量上限(maxSize 未设或设为 0 / -1)
- 无外部清理机制(如定时扫描、事件驱动淘汰、手动 purge)
- 监控显示内存持续单向增长,且堆 dump 中大量缓存条目 age 极高、key 分布离散(非热点集中)
硬核解法:从机制上堵死“只进不出”
不能靠事后 GC 或重启缓解,必须重构缓存生命周期控制:
-
强制启用逐出双保险:同时配置 TTL + 容量上限。例如 Caffeine 中:
expireAfterWrite(10, MINUTES).maximumSize(10_000);gcache 中必须用NewAdapterMemoryLru(5000)替代默认无限制适配器 -
拒绝“永不过期”语义:禁止使用
0或DurationNoExpire。即使业务要求“长期有效”,也应设为合理上限(如 7 天),并配合后台刷新(refreshAfterWrite) - 键空间必须收敛:禁用含用户输入、时间戳、UUID 的原始参数拼接 key。统一做归一化(如哈希截断、白名单校验、路径模板化),防止键爆炸式膨胀
-
引入弱引用兜底(仅限本地缓存):对非强一致性场景,改用
WeakHashMap或封装WeakReference<value></value>,使 JVM GC 可自然回收无强引用的条目
验证与防护:让退化不可发生
上线前必须闭环验证:
- 用
jcmd <pid> VM.native_memory summary</pid>或go tool pprof观察 native 内存是否随缓存增长同步上升 - 注入压力:模拟 24 小时连续写入,检查 RSS 是否线性增长、是否触发逐出
- 暴露指标:导出缓存 size、hitRate、evictionCount 到 Prometheus,设置告警:当
evictionCount == 0 && size > 0.9 * maxSize持续 5 分钟即触发
缓存不是垃圾桶,是带闸门的蓄水池。没有逐出策略的缓存,等于把内存租给数据终身使用——这不是泄漏,是主动交出管理权。










