问题背景:
有业务需要进行数据导出的操作,在进行导出任务超过15万行的数据导出时,出现了问题:
java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: GC overhead limit exceeded
关于为何会报错此信息:
在JVM中假如98%的时间是用于GC且可用的 Heap size 不足2%的时候将抛出此异常信息。
问题分析:
代码如下:
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet("书目导出");
/**
省略获取数据并向sheet页中写入的逻辑
*/
FileOutputStream fileOutputStream = new FileOutputStream(file);
//下面这行报的错
workbook.write(fileOutputStream);
fileOutputStream.close();
报错代码:workbook.write(fileOutputStream);这一行代码导致报错,初步定义问题时由于XSSFWorkbook:是操作Excel2007的版本,扩展名是.xlsx,在写入数据量超出65536条后就会报异常。
解决方案:
从POI 3.8版本开始,提供了一种基于XSSF的低内存占用的API,SXSSFWorkbook。
流媒体版本的XSSFWorkbook实现“BigGridDemo”策略。允许写入非常大的文件,而不会耗尽内存一个可配置的行部分在任何时间保存在内存中。
官方介绍:
SXSSF(软件包:org.apache.poi.xssf.streaming)是XSSF的API兼容流扩展,用于必须生成非常大的电子表格并且堆空间有限时使用。SXSSF 通过限制对滑动窗口中的行的访问来实现其低内存占用量,而 XSSF 则允许访问文档中的所有行。不再位于窗口中的旧行将变得不可访问,因为它们将写入磁盘。
滑动窗口:
一个窗口为 100 行的工作表。当行计数达到 101 时,rownum=0 的行被刷新到磁盘并从内存中删除,当 rownum 达到 102 时,rownum=1 的行被刷新,依此类推。
SXSSFWorkbook 如何降低内存
SXSSFWorkbook 默认使用内联字符串而不是共享字符串表。这是非常有效的,因为不需要将文档内容保存在内存中,但众所周知,这些文档会生成与某些客户端不兼容的文档。启用共享字符串后,文档中的所有唯一字符串都必须保存在内存中。根据您的文档内容,这可能比禁用共享字符串使用更多的资源。
压缩临时文件
SXSSF 在临时文件(每个工作表一个临时文件)中刷新工作表数据,并且这些临时文件的大小可能会增长到非常大的值。例如,对于 20 MB 的 csv 数据,临时 xml 的大小将超过 1 GB。如果临时文件的大小有问题,您可以告诉 SXSSF 使用 gzip 压缩:
SXSSFWorkbook wb = new SXSSFWorkbook();
wb.setCompressTempFiles(true);//临时文件将被压缩