mybatis二级缓存若未配置size、flushinterval和eviction三项参数,会导致缓存无限增长、旧数据不淘汰、对象无法回收,引发跨sqlsession内存泄漏;必须设size限制条目数、flushinterval定期刷新、eviction指定淘汰策略(如lru)。

MyBatis二级缓存若未配置合理的过期与淘汰策略,确实容易引发跨SqlSession的内存泄漏——不是缓存“不工作”,而是缓存“只进不出”,对象长期驻留堆中无法回收。
关键问题:缓存成了“只写不删”的黑洞
二级缓存默认不自动过期,一旦启用<cache></cache>却没设flushInterval、size或eviction,就等于开了个无底洞:
- size=0 或未设上限:缓存条目无限增长,尤其在高频查询+宽泛参数(如分页偏移量未归一化)时,Key大量重复堆积
- flushInterval="0" 或缺失:缓存永不刷新,旧数据永远不被淘汰,即使业务已不再访问
- eviction="NONE":明确关闭淘汰机制,连LRU/FIFO等基础兜底都没了
典型泄漏场景与表现
这类泄漏往往隐蔽:应用不报错、GC照常运行,但堆内存缓慢爬升,重启后回落,过几天又涨回来。常见触发点:
- 定时任务每小时查一次全量订单列表,结果缓存在二级缓存里,且没设size限制 → 几天后缓存存了几百万条记录
- 前端传参含时间戳、随机数等动态值,导致SQL参数每次不同 → 缓存Key爆炸式增长,实际命中率趋近于0,但缓存容器本身持续膨胀
- VO对象持有非序列化字段(如Spring代理、ThreadLocal引用),被缓存后整条引用链锁死,连带关联对象无法回收
必须配置的三项底线参数
哪怕只是临时启用二级缓存,也请至少补全这三项,避免失控:
- size="256":硬性限制缓存条目数,超出即触发淘汰(注意:这是每个Mapper namespace独立计数)
- flushInterval="300000"(5分钟):定期清空整个namespace缓存,兜底防 stale data 和内存滞留
- eviction="LRU":缓存满时自动踢出最久未用项,比FIFO更适应真实访问模式
示例配置:<cache eviction="LRU" flushinterval="300000" size="256" readonly></cache>
验证是否真正生效
光配了还不够,得确认它真在起作用:
- 启动时观察日志,确认MyBatis输出
Cache configuration for [xxxMapper] has been set - 用JMX或Actuator端点(如
/actuator/caches)查看缓存命中率、当前大小、最近清理时间 - 压测时dump堆内存,用MAT打开Histogram,筛选
org.apache.ibatis.cache.impl.PerpetualCache,看实例数是否随请求稳定而非线性增长
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











