如何解决Java for each块操作MongoDB时出现的堆内存溢出问题
问题根因
- 存在无意义的序列化/反序列化开销:遍历聚合返回结果时,拿到的
dbObject本身就是Document类型,你额外做了转JSON字符串→再解析回Document的操作,会生成大量临时字符串、JSON解析中间对象,平白消耗数倍内存,你的报错栈正好卡在JSON解析环节,就是这步操作直接触发的OOM。 - 全量加载数据再插入:你把所有聚合结果全部存入ArrayList再一次性执行插入,当返回的文档数量多、单条体积大时,无论你调多大堆内存,只要数据量超过内存上限就会触发OOM。
- 聚合配置不合理:设置了
allowDiskUse(false),如果聚合阶段处理的数据量超过MongoDB的内存限制,也会间接导致数据拉取环节出现内存异常。 - 额外逻辑BUG:你写的
$match条件里重复添加了key字段,后面的append("key", system)会直接覆盖前面的append("key", "PP"),导致第一个key条件失效,这个问题可以顺便修复。
解决方案
按优先级依次调整即可解决堆内存溢出问题:
删掉无意义的转译步骤
把循环里的documentList.add(Document.parse(dbObject.toJson()));直接替换为documentList.add(dbObject);,这一步就能减少至少2/3的临时内存占用,首先解决JSON解析环节的OOM。改成分批插入,避免全量加载到内存
不要把所有数据都存到ArrayList里,每攒固定条数就执行一次插入,插入后清空列表释放内存,示例代码如下:
// 每批插入1000条,可根据单条文档大小调整批次大小 int batchSize = 1000; List<Document> documentList = new ArrayList<>(batchSize); // 给游标设置批次大小,避免驱动一次性拉取过多数据到内存 AggregateIterable<Document> response = collectionDB.aggregate(pipeline) .allowDiskUse(true) // 聚合允许用磁盘缓存,适配大数据量聚合场景 .batchSize(batchSize); for (Document dbObject : response) { documentList.add(dbObject); // 攒够一批就插入 if (documentList.size() >= batchSize) { mergeCollection.insertMany(documentList); documentList.clear(); // 清空列表释放内存 } } // 插入最后不满一批的剩余数据 if (!documentList.isEmpty()) { mergeCollection.insertMany(documentList); }
- 若聚合数据量极大,可以适当调低
batchSize的数值,进一步降低内存占用。
内容的提问来源于stack exchange,提问作者Rose
相关产品推荐
相关产品推荐

