Java处理2GB以上ByteArrayInputStream大文件堆内存溢出问题求解
从你给出的错误栈可以看出,报错发生在method_Returns_FIle_In_ByteArrayInputStream_Format()方法内部的ByteArrayOutputStream扩容阶段,还没到你写的后续代码执行。ByteArrayOutputStream内部用字节数组存储所有数据,扩容时需要创建更大的新字节数组复制旧数据,而Java中数组长度用int类型定义,最大值为Integer.MAX_VALUE - 8(约1.99GB),你要处理的2.5GB文件已经超过了这个硬限制,所以就算调大JVM堆内存的-Xmx参数也不可能解决问题。
按优先级从高到低给出以下方案:
1. 重构数据来源方法(最优方案)
修改method_Returns_FIle_In_ByteArrayInputStream_Format()方法的实现,放弃用ByteArrayOutputStream全量缓存数据再转ByteArrayInputStream的逻辑,改为直接返回不依赖堆内全量缓存的InputStream实现:
- 如果数据来源于本地文件,直接返回
FileInputStream - 如果数据来源于网络请求,直接返回HTTP响应的
InputStream - 如果数据是程序动态生成的,改用
PipedInputStream+PipedOutputStream的管道流模式边生成边传递
改造后你的代码可以简化为:
InputStream is = method_Returns_Stream_Without_Full_Cache(); method_Where_To_send_Data_In_InputStream_Format(is);
全程只需要固定大小的缓冲区,内存占用稳定在KB级,不会有大内存占用问题。
2. 临时文件中转(兼容旧方法方案)
如果没有权限修改来源方法的实现,可以先把ByteArrayInputStream的数据写入本地临时文件中转,再通过FileInputStream读取传递给下游方法,中转过程只需要固定大小的缓冲区,内存占用极低:
File tempFile = File.createTempFile("large_file_transfer", ".tmp"); // 程序退出时自动删除临时文件 tempFile.deleteOnExit(); try (OutputStream os = new FileOutputStream(tempFile)) { ByteArrayInputStream bais = method_Returns_FIle_In_ByteArrayInputStream_Format(); byte[] buffer = new byte[8192]; int len; while ((len = bais.read(buffer)) != -1) { os.write(buffer, 0, len); } } // 读取临时文件流转给下游 try (InputStream is = new FileInputStream(tempFile)) { method_Where_To_send_Data_In_InputStream_Format(is); }
3. 堆外内存缓存(可选方案)
如果不允许写本地临时文件,可以用堆外内存(DirectByteBuffer)存储数据,不受堆内存限制和Java数组长度限制,可以用Apache Commons IO提供的ByteBufferInputStream封装堆外缓存传递给下游,不过堆外内存的分配和释放需要谨慎处理,避免出现内存泄漏。
如果下游的method_Where_To_send_Data_In_InputStream_Format()方法内部也会把输入流全量读取到字节数组,那你还需要同步修改下游方法的实现,支持分段读取流数据处理,否则还是会出现相同的OOM问题。
内容的提问来源于stack exchange,提问作者suchit

