问题现场:线上内存飙高,OOM 报警 📅 2026/6/26 2:54:24 线上老项目突然收到服务器内存使用率持续飙高的报警紧接着应用直接抛出 OOM 错误服务崩溃。紧急拉取了堆 Dump 文件用 JProfiler 打开后直接看到了内存占用的元凶大量com.alibaba.druid.proxy.jdbc相关对象堆积堆中最大的单个对象是一个char[]大小超过 500MB存储的正是项目中执行的 SQL 字符串结合项目业务场景初步判断是数据库操作相关的内存泄漏定位方向直接锁定了代码中的 SQL 操作和 Druid 连接池配置。二、根因定位双重问题叠加导致的灾难顺着堆 Dump 里的 SQL 文本我直接定位到了业务代码发现这次 OOM 是两个问题叠加导致的。1. 业务代码SQL 拼接逻辑导致大对象堆积这是一个老项目当年的开发同学已经离职了代码里存在这样的逻辑单条INSERT语句中通过循环拼接 SQL 字符串一次性插入大量数据当数据量较大时拼接后的 SQL 字符串会变得非常大生成的char[]对象直接占用几百 MB 内存这些大字符串被线程栈引用短时间内无法被 GC 回收直接推高了内存水位2. 框架层面Druid 1.1.22 版本的经典 SQL 缓存泄漏堆 Dump 中大量的 Druid 对象指向了一个更致命的问题Druid 连接池的 SQL 统计缓存。项目使用的 Druid 版本是1.1.22这个版本存在一个广为人知的问题SQL 统计功能会无限制缓存所有执行过的 SQL 字符串无法自动清理项目中拼接的大量不同 SQL会被 Druid 全部缓存到sqlStatMap中这些对象会一直持有 SQL 字符串的引用导致它们无法被 GC 回收随着服务运行时间增长缓存的 SQL 越来越多内存只会涨不会跌最终撑满堆内存触发 OOM三、解决方案两步走彻底根治问题针对这两个问题我们采用了业务框架双管齐下的修复方案从根源解决内存泄漏。第一步优化 Druid 配置掐断缓存泄漏直接修改项目的 Druid 配置关闭无限制的 SQL 统计同时限制缓存大小避免内存无限增长。方案 A彻底关闭 SQL 统计推荐零泄漏风险spring: datasource: druid: filter: stat: enabled: false # 关闭导致内存泄漏的SQL统计 web-stat-filter: enabled: false # 关闭Web统计减少额外内存占用方案 B保留监控限制缓存大小折中方案如果业务必须保留 SQL 监控可以通过配置限制缓存的 SQL 数量避免无限增长spring: datasource: druid: filter: stat: enabled: true max-stat-count: 200 # 限制最多缓存200条SQL超出自动淘汰第二步重构业务代码替换 SQL 拼接为批量插入修改原有的 SQL 拼接逻辑改为标准的批量插入方式既避免了超大 SQL 字符串的生成也提升了数据库写入性能。改造前问题代码// 循环拼接SQL生成超大字符串 StringBuilder sql new StringBuilder(INSERT INTO t_invoice (col1, col2) VALUES ); for (Invoice invoice : list) { sql.append((?, ?),); } jdbcTemplate.update(sql.toString(), params);改造后批量插入// 使用JdbcTemplate批量插入避免生成超大SQL字符串 String sql INSERT INTO t_invoice (col1, col2) VALUES (?, ?); jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { Override public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setString(1, list.get(i).getCol1()); ps.setString(2, list.get(i).getCol2()); } Override public int getBatchSize() { return list.size(); } });四、效果验证与后续优化改造完成后我们重新上线服务并进行了压测验证内存曲线恢复平稳不再出现持续飙高的情况堆 Dump 中 Druid 相关对象和大char[]基本消失数据库写入性能也有明显提升单批次插入耗时降低了 40%额外优化建议对于老项目建议升级 Druid 到最新稳定版如1.2.20修复了大量已知的内存泄漏问题批量插入时建议设置合理的批次大小如每批 100-500 条避免单次操作过大导致数据库压力上线前务必进行压测通过 JProfiler 或 Arthas 观察内存变化提前发现潜在问题五、踩坑总结这次 OOM 排查给了我两个深刻的教训老项目的依赖版本一定要关注Druid 1.1.22 这个版本的 SQL 缓存泄漏问题非常普遍很多线上 OOM 都源于此升级或关闭统计是最直接的解决方式。