news 2026/6/26 2:54:20

问题现场:线上内存飙高,OOM 报警

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
问题现场:线上内存飙高,OOM 报警

,线上老项目突然收到服务器内存使用率持续飙高的报警,紧接着应用直接抛出 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(); } });

四、效果验证与后续优化

改造完成后,我们重新上线服务并进行了压测验证:

  1. 内存曲线恢复平稳,不再出现持续飙高的情况
  2. 堆 Dump 中 Druid 相关对象和大char[]基本消失
  3. 数据库写入性能也有明显提升,单批次插入耗时降低了 40%

额外优化建议

  • 对于老项目,建议升级 Druid 到最新稳定版(如1.2.20+),修复了大量已知的内存泄漏问题
  • 批量插入时,建议设置合理的批次大小(如每批 100-500 条),避免单次操作过大导致数据库压力
  • 上线前务必进行压测,通过 JProfiler 或 Arthas 观察内存变化,提前发现潜在问题

五、踩坑总结

这次 OOM 排查给了我两个深刻的教训:

  1. 老项目的依赖版本一定要关注:Druid 1.1.22 这个版本的 SQL 缓存泄漏问题非常普遍,很多线上 OOM 都源于此,升级或关闭统计是最直接的解决方式。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/26 2:53:16

记一次因为服务器和数据库时间不统一导致的数据入库问题排查

缘起: 下午在将测试服务迁移到预生产环境后,由于预生产环境属于内网环境,数据库使用公网访问。未确认时间差问题。将服务部署以后运行无任何报错,然后数据未能成功入库。事务处理结果都能从库里查询出结果,但是…

作者头像 李华
网站建设 2026/6/26 2:50:56

只有156页的《百页大模型原理》出版

学大模型最痛苦的事是什么? 要么是短视频碎片化知识点,越看越混乱,Transformer、自注意力、KV 缓存听过无数遍,一深究就卡壳;要么是动辄五六百页的厚重教材,公式堆砌、篇幅冗长,上班族、零基础…

作者头像 李华
网站建设 2026/6/26 2:49:44

它解决的不是“写代码”,而是“盯流程”

你告诉它要处理哪些 Story、用什么执行策略,它就自动完成「创建规格 → 开发实现 → 测试自动化 → 代码审查 → 回顾」这条流水线,只在真的需要人类决策时才打断你。 这和普通“单命令跑一个 Skill”不一样。它更像一个构建周期编排器: 初始…

作者头像 李华
网站建设 2026/6/26 2:47:03

软件个性化服务中的用户画像构建

在数字化时代,软件个性化服务已成为提升用户体验的关键。无论是购物平台、音乐APP还是新闻推荐系统,精准的个性化服务都离不开用户画像的构建。用户画像是通过收集和分析用户行为、偏好、社交关系等多维度数据,形成的虚拟用户模型。它不仅帮助…

作者头像 李华
网站建设 2026/6/26 2:46:49

Spring MVC 将 Jackson 序列化器替换为 FastJson2 序列化器

项目版本与运行环境 JDK 版本&#xff1a;17操作系统&#xff1a;Windows 11SpringBoot 版本&#xff1a;3.5.14 引入依赖<!-- FastJson2 核心 --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId&g…

作者头像 李华