Spring Batch处理1TB数据OOM,重启后堆未释放问题求助
我们通过Spring Batch作业处理1TB数据,运行4-5小时后出现
java.lang.OutOfMemoryError: Java heap space错误。理想情况下,作业终止后Java堆应被清理,重启后应使用全新堆,但通过jcmd -gccapacity检查发现OGC和OC值仍显示已满。
我们已设置-Xmx为8GB,生产环境出现该问题,低环境因数据量小未触发,目前正在排查代码内存泄漏。请问如何清理堆并让作业像全新运行一样重启?
附jcmd -gccapacity输出:NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC CGC 0.0 4194304.0 220160.0 0.0 0.0 220160.0 0.0 4194304.0 3974144.0 3974144.0 0.0 1224704.0 203840.0 0.0 1048576.0 28672.0 112 4 83
澄清jcmd -gccapacity的指标误解
你看到的OGC(老年代当前容量)和OC(老年代已分配容量)是JVM向操作系统申请的内存块大小,并非堆中实际被对象占用的内存。JVM重启后,之前的所有对象都会被销毁,但为了避免频繁向OS申请/释放内存的开销,JVM会保留已申请的内存空间,这是正常行为。
如果要确认堆是否真的被清理,应该用jcmd <pid> GC.heap_info或者jstat -gc <pid>查看已使用内存(比如jstat里的OU列是老年代已使用大小),而不是gccapacity里的容量指标。
让JVM重启后归还内存给OS的配置
如果希望JVM在重启后或GC后主动归还未使用的内存给OS,可以添加以下JVM参数:
- 对于G1GC(现代JVM默认):
-XX:MaxHeapFreeRatio=70 -XX:MinHeapFreeRatio=40 -XX:+ExplicitGCInvokesConcurrent -XX:+UnlockExperimentalVMOptions -XX:G1HeapRegionSize=16m - 对于ParallelGC:
-XX:MaxHeapFreeRatio=70 -XX:MinHeapFreeRatio=40
这些参数会让GC后自动收缩堆内存,将未使用的部分归还OS。但注意:频繁的内存收缩会带来性能损耗,需权衡使用。
解决Spring Batch的OOM问题
作业配置优化
- 调小Chunk Size:默认Chunk Size是1000,处理大对象时建议降到100甚至更小,减少单次加载到内存的数据量。
- 使用分页读取器:用
JdbcPagingItemReader替代JdbcCursorItemReader,避免一次性加载全量ResultSet到内存。 - 及时释放引用:在ItemProcessor/ItemWriter中不要持有对象的长期引用,尤其是静态集合、单例Bean中的缓存,处理完立即清空或移除无用数据。
内存泄漏排查
- 导出堆快照:用
jcmd <pid> GC.heap_dump ./heap.hprof导出堆快照,用MAT(Memory Analyzer Tool)分析大对象、重复创建的对象,以及未释放的引用(比如ThreadLocal未清理、单例Bean中的集合持续累加)。 - 开启GC日志:添加以下参数记录GC细节,分析内存增长趋势:
-Xloggc:/var/log/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC
如果老年代内存持续增长不回落,大概率是存在内存泄漏;如果是临时峰值导致OOM,调整Chunk Size或堆参数即可。
- 清理JobRepository历史数据:如果用数据库存储作业执行记录,定期清理不需要的历史数据,避免查询时加载大量冗余对象。
应急重启方案
最彻底的“全新运行”方式就是重启整个JVM进程(容器化环境则重启容器),这会完全销毁旧堆,重新初始化全新的堆空间。不要依赖手动触发GC(比如jcmd <pid> GC.run),手动GC只能临时回收内存,无法解决内存泄漏导致的OOM问题。
内容的提问来源于stack exchange,提问作者Chris BK

