You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

异步压缩NIO Path集合时出现Java heap out of memory问题求助

堆内存溢出根因

你当前的压缩逻辑核心问题是将整个ZIP包全量缓存到堆内存中,并发场景下堆内存占用会随任务数、压缩文件总大小线性增长,最终触发OOM,具体问题点如下:

  • 全量堆内缓存:你使用ByteArrayOutputStream存储压缩后的全量ZIP二进制数据,调用toByteArray()时还会额外复制一份同等大小的字节数组,单个压缩任务就会占用「ZIP包体积 * 2」的堆内存,多并发处理大文件时会快速耗尽堆空间
  • 资源泄漏隐患:代码中读取源文件的FileInputStream没有主动关闭,长时间运行会累计文件句柄泄漏、堆内存溢出风险
  • 无并发控制逻辑:未限制压缩任务的最大并行数,短时间内提交多个大文件压缩任务时,堆内存占用会直接超出上限

修复方案

方案1:边压缩边上传,无内存中转(推荐)

通过管道流实现压缩逻辑和SFTP上传并行,完全不需要在内存中缓存完整ZIP包:

public InputStream compressToZip(String s3folderName, Set<Path> paths) throws Exception {
    PipedInputStream pipedIn = new PipedInputStream(8192 * 1024); // 设置8M缓冲区
    // 异步执行压缩逻辑,边压缩边写入管道流
    CompletableFuture.runAsync(() -> {
        try (ZipOutputStream zos = new ZipOutputStream(new PipedOutputStream(pipedIn))) {
            for (Path path : paths) {
                zos.putNextEntry(new ZipEntry(path.getFileName().toString()));
                // try-with-resources自动关闭文件输入流
                try (InputStream fis = Files.newInputStream(path)) {
                    IOUtils.copy(fis, zos);
                }
                zos.closeEntry();
            }
        } catch (Exception e) {
            // 自行补充异常处理逻辑
        }
    });
    return pipedIn;
}

调用侧代码不需要做任何修改,直接使用返回的输入流写入SFTP即可。

方案2:临时文件中转(稳定性更高)

如果担心管道流的异步异常不好处理,可以将压缩包先写入本地临时文件,完全不占用堆内存,流关闭时自动删除临时文件:

public InputStream compressToZip(String s3folderName, Set<Path> paths) throws Exception {
    // 创建临时压缩文件
    Path tempZipPath = Files.createTempFile("compress_temp_", ".zip");
    try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(tempZipPath))) {
        for (Path path : paths) {
            zos.putNextEntry(new ZipEntry(path.getFileName().toString()));
            try (InputStream fis = Files.newInputStream(path)) {
                IOUtils.copy(fis, zos);
            }
            zos.closeEntry();
        }
    }
    // 返回文件输入流,重写close方法实现流关闭后自动删除临时文件
    return new FileInputStream(tempZipPath.toFile()) {
        @Override
        public void close() throws IOException {
            super.close();
            Files.deleteIfExists(tempZipPath);
        }
    };
}

额外优化建议

  • 压缩任务统一提交到固定核心线程数的线程池执行,限制最大并行数,避免并发过高导致内存、IO资源耗尽
  • 若仍需要使用内存中转方案,可先统计待压缩文件总大小,超过阈值时自动降级到临时文件方案
  • 所有IO操作均使用try-with-resources语法自动释放资源,避免泄漏

内容的提问来源于stack exchange,提问作者mwKART

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 14:48:02