EasyExcel 导出 Excel 时出现 "Can not close IO" 问题的分析与解决
【免费下载链接】easyexcel 快速、简洁、解决大文件内存溢出的java处理Excel工具 项目地址: https://gitcode.com/gh_mirrors/ea/easyexcel
问题背景
在使用阿里巴巴开源的 EasyExcel 库进行 Excel 导出操作时,开发者遇到了一个棘手的问题:当尝试导出包含大量图片数据的 Excel 文件时,系统抛出 "Can not close IO" 异常,并伴随内存溢出(OOM)问题。
问题现象
具体表现为:
- 导出过程中抛出 "Can not close IO" 异常
- 查看日志发现系统频繁触发 Full GC
- 最终导致 JVM 内存溢出
- 导出的 Excel 文件中图片数量较多时问题尤为明显
技术分析
根本原因
经过深入分析,发现问题并非简单的 IO 关闭异常,而是由于以下原因导致的:
- 内存管理问题:在关闭流时,EasyExcel 内部会进行数组扩充操作
- 大对象分配:图片数据作为大对象直接进入老年代
- 内存压力:当导出大量图片时,老年代空间迅速被占满
- GC 频繁:系统不断尝试 Full GC 来释放内存,但效果不佳
具体流程
- 自定义的
CustomUrlImageConverter将图片 URL 转换为字节数组 - 转换过程中使用
IoUtils.toByteArray读取图片数据 - 大量图片数据导致内存中积累了多个大字节数组
- 在最终关闭输出流时,EasyExcel 内部需要重组这些数据
- 重组过程需要额外的内存空间,而此时老年代已满
解决方案
临时解决方案
-
升级依赖库:将
commons-compress升级到 1.19 或更高版本- 这个版本优化了内存管理策略
- 减少了不必要的大数组分配
-
调整 JVM 参数:
-Xms2g -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200- 增加堆内存大小
- 使用 G1 垃圾收集器
- 设置合理的 GC 停顿时间
长期优化方案
-
分批处理图片数据:
// 分批次写入数据 int batchSize = 100; for (int i = 0; i < totalCount; i += batchSize) { List<TestVo> batchList = list.subList(i, Math.min(i + batchSize, totalCount)); excelWriter.write(batchList, writeSheet); } -
优化图片处理:
- 压缩图片质量
- 限制图片尺寸
- 使用缓存机制避免重复读取
-
使用磁盘缓存:
// 在写入配置中启用临时文件 ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()) .tempFile("temp") // 指定临时文件目录 .build();
最佳实践建议
-
监控内存使用:在导出操作前后添加内存监控代码
Runtime runtime = Runtime.getRuntime(); long usedMemory = runtime.totalMemory() - runtime.freeMemory(); logger.info("Memory used: {} MB", usedMemory / (1024 * 1024)); -
设置合理的超时时间:对于大数据量导出,适当延长请求超时时间
-
提供进度反馈:对于长时间运行的导出操作,提供进度提示
-
考虑异步导出:对于特别大的数据集,采用异步导出+通知下载的方式
总结
EasyExcel 导出 Excel 时出现的 "Can not close IO" 问题,本质上是内存管理问题。通过升级依赖库、优化内存配置和改进数据处理方式,可以有效解决这一问题。对于处理包含大量图片等二进制数据的 Excel 导出场景,建议采用分批处理、数据压缩和磁盘缓存等策略,以确保系统的稳定性和性能。
【免费下载链接】easyexcel 快速、简洁、解决大文件内存溢出的java处理Excel工具 项目地址: https://gitcode.com/gh_mirrors/ea/easyexcel
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



