ArrayList本身不会直接导致内存溢出,但一次性加载超大数据集(如百万级记录、GB级数组)极易触发OutOfMemoryError;分批处理通过控制单批数据量(100–500条)、及时清空引用、配合数据库分页与流式读取,切断内存失控链。

ArrayList 本身不会直接导致内存溢出,但当它被用来一次性加载、缓存或传输**超大数据集**(比如几十万条数据库记录、GB级字节数组)时,极易触发 java.lang.OutOfMemoryError: Java heap space。分批处理不是“绕过”问题,而是从数据规模和生命周期两个维度切断内存失控链——核心在于:不让单个 ArrayList 持有全部数据,也不让大批量对象长期驻留堆中。
为什么 ArrayList 容易引发 OOM?
常见高危场景包括:
- 从数据库
SELECT *读取百万级记录,全塞进一个new ArrayList() - 循环中不断
list.add(new byte[1024 * 1024])创建大数组 - 静态集合(如
static List<data> cache</data>)持续累积未清理的对象 - 流式处理误用
collect(Collectors.toList())把整个结果集拉入内存
分批处理的关键设计原则
分批不是简单切数组,而是配合生命周期管理:
- 批次大小要可控:通常 100–500 条/批较稳妥(取决于单条数据大小)。避免设为 10000+,否则单批仍可能 OOM
-
每批处理完立即丢弃引用:用完即清空局部
List<t></t>,不保留、不缓存、不跨批复用 -
避免嵌套大集合:不要用
List<list>></list>存所有批次,而应逐批生成、处理、释放 -
配合数据库游标或分页查询:后端不查全量,改用
LIMIT offset, size或 MyBatis 的fetchSize流式读取
典型安全分批写法(无多线程)
以处理数据库查询结果为例:
int pageSize = 300;
int offset = 0;
do {
List<data> batch = dataMapper.selectByPage(offset, pageSize); // 数据库分页查
if (batch.isEmpty()) break;
<pre class="brush:php;toolbar:false;">processBatch(batch); // 处理当前批(插入/转换/发送等)
batch.clear(); // 显式清空引用(虽局部变量会自动回收,但明确更安全)
batch = null; // 辅助 GC,尤其在长循环中
offset += pageSize;
} while (true);
⚠️ 注意:processBatch() 内部也需避免新建大集合;若需暂存中间结果,同样按小批次操作。
结合批量操作与资源释放
例如 JDBC 批量插入:
- 用
PreparedStatement.addBatch()累积 300 条后调用executeBatch(),再clearBatch() - 每次执行后主动
System.gc()不推荐,但可调用connection.commit()释放事务锁和连接缓冲区 - 确保
ResultSet使用setFetchSize(100)启用流式读取,防止驱动缓存全量结果
分批处理不能替代内存泄漏排查,但它是最直接、最易落地的 OOM 防御手段——把“一口吃成胖子”变成“小口嚼碎咽下”,让 GC 始终有空间回收旧对象。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











