文件下载ZIP报错:Out of Memory的问题排查
废话少说,上干货!!
项目中下载小文件或者下载成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完全没有内存溢出错误了。
原文地址:https://blog.csdn.net/weixin_38203586/article/details/145117583
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!