Java文件下载ZIP报错:Out of Memory的问题排查

 更新时间:2025年01月14日 11:23:11   作者:山林竹笋  
本文主要介绍了Java项目中下载大文件(超过2G的ZIP文件)时出现内存溢出(OutOfMemory:JavaHeapSpace)的问题,具有一定的参考价值,感兴趣的可以了解一下

 废话少说,上干货!!

项目中下载小文件或者下载成ZIP文件都正常,但下载超过2G的ZIP文件,报错:Out of Memory:Java Heap Space。

内存溢出,可是大毛病,排查一下代码:

 private void setByteArrayOutputStream(String fileName, InputStream inputStream, ZipArchiveOutputStream zous) {
       
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while ((len = inputStream.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }
        baos.flush();
        byte[] bytes = baos.toByteArray();
        //设置文件名
        ArchiveEntry entry = new ZipArchiveEntry(fileName);
        zous.putArchiveEntry(entry);
        zous.write(bytes);
        zous.closeArchiveEntry();
        baos.close();
}

细心的童鞋应该已经发现错误了!

一、原因分析

原因分析主要与 ByteArrayOutputStream 的使用有关。ByteArrayOutputStream 会在内存中动态扩展其缓冲区以容纳写入的数据。当写入大量数据时,尤其是在处理大文件时,可能会导致内存频繁分配和复制,从而消耗大量内存。大文件处理:如果输入流(InputStream)读取的数据量很大(如超过几百MB或GB),ByteArrayOutputStream 可能会消耗超过可用内存的资源,导致 OutOfMemoryError。

在上述代码中,逻辑如下:

(1)读取数据:通过 inputStream.read(buffer) 持续将数据读取到缓冲区 buffer 中。

(2)写入 ByteArrayOutputStream:每次读取的数据都被写入到 ByteArrayOutputStream 中。如果文件非常大,ByteArrayOutputStream 将不断扩展其内部数组。

(3) 转换为字节数组:调用 baos.toByteArray() 时,会创建一个新的字节数组并将所有数据复制到这个新数组中,这又需要额外的内存。

二、解决方案

采用流式处理:直接从 InputStream 读取并写入到 ZipArchiveOutputStream,而不使用 ByteArrayOutputStream。这样可以避免将整个文件加载到内存中。

代码如下:

​private void setByteArrayOutputStream(String fileName, InputStream inputStream, ZipArchiveOutputStream zous) {
        //创建Zip入口
        try {
            ZipArchiveEntry entry = new ZipArchiveEntry(fileName);
            zous.putArchiveEntry(entry);

            //使用流缓冲区,一次1024字节,分块读取并写入ZIP输出流
            byte[] buffer = new byte[1024];
            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                zous.write(buffer, 0, len);
            }
            //完成当前ZIP入口
            zous.closeArchiveEntry();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            //确保输入流被关闭
            try {
                inputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

运行之后,我们发现:不论多大的文件下载,不再报错内存溢出了。但对于超过了4G的文件,在形成ZIP包时,出现了其他错误,如下:

org.apache.commons.compress.archivers.zip.Zip64RequiredException: XXXXXXXXXXXXXXXXXXXXXXXXXXXX's size exceeds the limit of 4GByte.
	at org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream.checkIfNeedsZip64(ZipArchiveOutputStream.java:651)
	at org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream.handleSizesAndCrc(ZipArchiveOutputStream.java:638)
	at org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream.closeArchiveEntry(ZipArchiveOutputStream.java:513)

原因分析:

这个异常通常是在尝试创建一个 ZIP 文件时遇到的,特别是当文件的大小超过 4GB 时。ZIP 格式的标准限制了单个文件的大小为 4GB,因此在处理大文件时需要使用 ZIP64 格式。我们在代码里添加如下:

zous.setUseZip64(ZipArchiveOutputStream.Zip64Mode.Always); // 启用 ZIP64 支持
或者(不同版本,函数不一样)
zous.setUseZip64(Zip64Mode.Always);

按照以上改完之后,我们发现文件下载ZIP完全没有内存溢出错误了。

到此这篇关于Java文件下载ZIP报错:Out of Memory的问题排查的文章就介绍到这了,更多相关Java文件下载ZIP报错内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java并发无锁多线程单线程示例详解

    java并发无锁多线程单线程示例详解

    这篇文章主要为大家介绍了java并发无锁多线程单线程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • 关于IntelliJ IDEA 打包代码报错的问题

    关于IntelliJ IDEA 打包代码报错的问题

    这篇文章主要介绍了关于IntelliJ IDEA 打包代码报错的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • spring源码阅读--aop实现原理讲解

    spring源码阅读--aop实现原理讲解

    这篇文章主要介绍了spring源码阅读--aop实现原理讲解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • 详解MyBatis动态代理机制

    详解MyBatis动态代理机制

    MyBatis的动态代理机制是其核心特性之一,通过该机制,开发者可以专注于定义接口和 SQL 映射,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-03-03
  • 使用kafka-console-consumer.sh不停报WARN的问题及解决

    使用kafka-console-consumer.sh不停报WARN的问题及解决

    这篇文章主要介绍了使用kafka-console-consumer.sh不停报WARN的问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • Java实现十秒向MySQL插入百万条数据

    Java实现十秒向MySQL插入百万条数据

    这篇文章主要为大家详细介绍了Java如何实现十秒向MySQL插入百万条数据,文中的示例代码讲解详细,对我们学习或工作有一定借鉴价值,需要的可以参考一下
    2022-11-11
  • Java二分查找算法与数组处理的应用实例

    Java二分查找算法与数组处理的应用实例

    二分查找法,又叫做折半查找法,它是一种效率较高的查找方法。数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同。Java 语言中提供的数组是用来存储固定大小的同类型元素
    2022-07-07
  • SpringBoot+EasyExcel实现自定义复杂样式导入导出

    SpringBoot+EasyExcel实现自定义复杂样式导入导出

    这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下
    2025-07-07
  • Java实现百万数据分批次插入的最佳实践分享

    Java实现百万数据分批次插入的最佳实践分享

    在当今数据驱动的应用开发中,处理大批量数据插入是后端开发人员经常面临的挑战,随着业务规模的扩大,数据量呈现爆发式增长,传统单条插入方式在面对百万级数据时显得力不从心,所以本文给大家分享了Java实现百万数据分批次插入的最佳实践,需要的朋友可以参考下
    2025-07-07
  • Java中自己如何实现log2(N)

    Java中自己如何实现log2(N)

    这篇文章主要介绍了Java中自己实现log2(N)的方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08

最新评论