【发布时间】:2021-08-05 13:46:21
【问题描述】:
我正在尝试处理一个包含多个要处理的内部 XML 文件的巨大 tar 文件。我有磁盘限制,所以我必须使用 uncompress-in-memory-do-stuff-then-forget 方法。我正在使用Apache Compress 库。到目前为止一切顺利,一切正常,但我现在想并行处理文件以缩短执行时间。为此,我们使用了反应器。
这似乎一直在工作,直到由于某种原因,数据在处理后没有被清理,所以它最终出现java.lang.OutOfMemoryError: Java heap space 异常。我考虑过的其他事情是调整垃圾收集器以更快地介入,但我有点迷茫,所以我很感激任何帮助。
进入代码。这是我正在使用的Flux.generate。对于每个下一个请求的项目,它会生成一个字节数组,其中包含来自 tar 文件的一个未压缩文件。看起来像这样:
(ArchiveInputStream tarArchive, SynchronousSink<byte[]> sink) -> {
process: try {
ArchiveEntry entry = null;
while ((entry = tarArchive.getNextEntry()) != null) {
if (entry.isDirectory()) continue;
log.info(entry.getName());
var bytes = IOUtils.readRange(tarArchive, (int) entry.getSize());
sink.next(bytes);
break process;
}
log.info("done!!!!");
sink.complete();
} catch (IOException e) {
log.error("Exception getting file entry", e);
sink.error(e);
}
return tarArchive;
}
稍后,字节数组被转换为 ByteArrayInputStream 被 JAXB 映射到内部模型中:
public Feed processElement(byte[] file) {
var feed = new Feed();
if (file.length != 0) {
try(var is = new ByteArrayInputStream(file)) {
var jaxbUnmarshaller = jaxbContext.createUnmarshaller();
feed = (Feed) jaxbUnmarshaller.unmarshal(is);
} catch (Exception ex) {
log.error("Error during XML unmarshal", ex);
}
}
return feed;
}
流并行化如下:
uncompressFiles(tarFileName)
.parallel()
.runOn(parallel())
.map(parser::processElement)
.sequential()
//.....
如果您对这种特殊情况或如何解决内存泄漏有任何见解,请告诉我。
【问题讨论】:
-
调整垃圾收集器不太可能解决您的问题(假设错误消息“Java 堆空间”,而不是“超出 GC 开销限制”),因为 Java 将在抛出之前执行完整的垃圾收集那个错误。
-
是的,你是对的,它是 java.lang.OutOfMemoryError: Java heap space。我会更新de描述。我最初的猜测是引用挂在某处(可能与文件字节数组有关),但我无法找到它,所以我开始寻找其他可能性。
-
您确定引用被保留吗?您应该能够通过堆转储来确认这一点。这也可以通过并行处理将使用更多工作内存这一事实来解释。有多少并行进程正在运行?
-
parallel()默认使用可用内核的数量,但您可以调整它。我尝试了几种工作线程和 JVM 最大堆配置,最终结果是相同的。虽然,我注意到删除并行进程可以保持内存稳定,但是将并行处理设置为 1 并没有,所以问题可能与将字节数组从主线程中从 tar 文件中提取到工作线程的移动有关线程。 -
另一方面,如何进行堆转储?
标签: java memory-leaks jaxb compression project-reactor