
























大家好,今天分享一次真实的线上 OOM 排查过程,踩坑 Druid 连接池的经典内存泄漏问题,以及完整的解决思路。
某天,线上老项目突然收到服务器内存使用率持续飙高的报警,紧接着应用直接抛出 OOM 错误,服务崩溃。
紧急拉取了堆 Dump 文件,用 JProfiler 打开后,直接看到了内存占用的元凶:

com.alibaba.druid.proxy.jdbc 相关对象堆积char[],大小超过 500MB,存储的正是项目中执行的 SQL 字符串结合项目业务场景,初步判断是数据库操作相关的内存泄漏,定位方向直接锁定了代码中的 SQL 操作和 Druid 连接池配置。
顺着堆 Dump 里的 SQL 文本,我直接定位到了业务代码,发现这次 OOM 是两个问题叠加导致的。
这是一个老项目,当年的开发同学已经离职了,代码里存在这样的逻辑:
INSERT 语句中,通过循环拼接 SQL 字符串,一次性插入大量数据char[] 对象直接占用几百 MB 内存堆 Dump 中大量的 Druid 对象,指向了一个更致命的问题:Druid 连接池的 SQL 统计缓存。
1.1.22,这个版本存在一个广为人知的问题:SQL 统计功能会无限制缓存所有执行过的 SQL 字符串,无法自动清理sqlStatMap 中,这些对象会一直持有 SQL 字符串的引用,导致它们无法被 GC 回收针对这两个问题,我们采用了业务+框架双管齐下的修复方案,从根源解决内存泄漏。
直接修改项目的 Druid 配置,关闭无限制的 SQL 统计,同时限制缓存大小,避免内存无限增长。
spring:
datasource:
druid:
filter:
stat:
enabled: false # 关闭导致内存泄漏的SQL统计
web-stat-filter:
enabled: false # 关闭Web统计,减少额外内存占用
如果业务必须保留 SQL 监控,可以通过配置限制缓存的 SQL 数量,避免无限增长:
spring:
datasource:
druid:
filter:
stat:
enabled: true
max-stat-count: 200 # 限制最多缓存200条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();
}
});
改造完成后,我们重新上线服务并进行了压测验证:
char[] 基本消失1.2.20+),修复了大量已知的内存泄漏问题这次 OOM 排查给了我两个深刻的教训:
希望这次分享能帮到遇到同样问题的朋友,如果你也遇到了 Druid 相关的内存问题,欢迎在评论区交流讨论~
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。