Spring Boot执行保存查询后堆内存(RAM)未释放问题排查
大数量数据操作后内存未回收问题排查与解决
问题现象
- 向MySQL插入40万条数据后,堆内存及RAM占用持续上升且无回落;重复执行同类操作时,堆内存额外上涨5-10%
- 查询120万条数据填满堆内存后,需等待20-25分钟堆内存才开始下降,期间有其他请求时服务器极易崩溃
- EC2环境中问题加剧:GC完全未触发,已出现3次内存耗尽导致的崩溃
- 使用
@Async线程(1个主线程调用另外2个不同Bean的@Async线程),线程执行完毕后堆内存仍未释放;未使用@Cacheable注解,仅执行数据查询、处理及更新操作,无输入流等资源泄漏场景 - 尝试过常规OOM排查方案但未解决问题
可能原因
1. GC策略与堆内存配置不匹配
- 默认GC(Parallel/CMS)在大内存场景下,Full GC触发阈值过高,导致内存回收延迟;EC2环境因云资源调度特性,GC线程优先级被压低,无法及时执行回收
- 堆内存分区比例不合理:新生代过小,大量对象直接进入老年代,只有老年代满额才触发Full GC,而Full GC耗时久、触发不及时
2. 隐性内存泄漏
- ORM框架缓存:Hibernate一级/二级缓存、MyBatis Session级缓存未及时清理,大量对象被缓存持有无法回收
- 线程池资源残留:
@Async默认线程池每次创建新线程,若自定义线程池配置不合理(核心线程数过大、队列无界),线程持有的ThreadLocal或上下文资源无法释放 - 数据库连接池问题:连接池最小连接数过大、连接未正常释放,导致连接对象长期占用内存
3. JVM参数适配问题
- 未设置合理的
-Xmx/-Xms,堆内存无上限或初始/最大堆差值过大,GC无法有效触发 - 未配置GC日志,无法准确分析GC触发时机与回收效果;EC2环境未适配内存overcommit特性,导致JVM内存被系统强制占用
修复方案
1. 调整GC策略与堆配置
- 切换为G1GC(适配大内存场景),配置参数:
让GC更主动地触发回收,控制停顿时间-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=45 - 合理分配堆内存分区:设置
-XX:NewRatio=2(新生代占1/3,老年代占2/3),避免大量对象直接进入老年代 - EC2环境关闭内存overcommit:修改系统参数
vm.overcommit_memory=2,防止JVM内存被系统强制回收
2. 排查并修复隐性泄漏
- 清理ORM缓存:禁用未使用的Hibernate二级缓存,配置缓存过期策略;MyBatis批量操作后手动清理Session缓存
- 优化
@Async线程池:自定义线程池,设置合理的核心线程数、最大线程数和队列大小,配置线程空闲超时(如keepAliveSeconds=60),避免线程长期持有资源 - 生成堆快照分析:使用
jmap生成堆快照:
通过MAT工具分析大对象、未回收对象的引用链,准确定位泄漏点jmap -dump:format=b,file=heap.hprof <进程ID>
3. 优化数据操作逻辑
- 分批处理数据:插入/查询时每次处理1万条以内数据,避免一次性加载大量对象到内存
- 批量操作优化:JDBC批量操作时关闭自动提交(
setAutoCommit(false)),减少事务开销与内存占用 - 结果集拉取优化:MyBatis或JDBC设置
fetchSize,避免一次性拉取所有查询结果到内存
4. 开启GC日志监控
- 添加JVM参数记录GC日志:
通过分析日志确认GC是否触发、回收效果,区分是GC延迟还是内存泄漏问题-Xloggc:/var/log/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC
内容的提问来源于stack exchange,提问作者Michael Bat
相关产品推荐
相关产品推荐

