You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Apache POI Stream API导出150K+数据时遭遇OutOfMemoryError

解决Apache POI 3.14 SXSSF导出大数据仍内存溢出的问题

我完全懂你现在的困扰:明明用了专门处理大数据量的SXSSF组件,结果还是碰到了OOM,而且你不想靠调大Java堆内存这种“治标不治本”的方法来解决。咱们一步步拆解问题,找到根源和可行的解决方案。

可能的问题根源

先理清楚SXSSF的核心逻辑:它会把超出内存阈值的行写入临时文件,只保留指定数量的行在内存中。但如果你的实现有这些细节没注意,还是会导致内存爆掉:

  • SXSSF默认内存保留行数过高:POI 3.14里SXSSFWorkbook默认会保留100行在内存里,数据量极大时这个阈值还是会让内存吃紧;
  • 一次性加载全量数据到内存:你把数据库查询的所有结果都塞进List<TransactionDTO>里,这相当于先把所有数据都占满内存,SXSSF的磁盘缓存优化完全没了用武之地;
  • 导出逻辑的隐藏内存消耗:比如重复创建单元格样式、大量字符串未复用,或者资源释放不彻底。

针对性解决方案

1. 手动缩小SXSSF的内存保留窗口

创建SXSSFWorkbook时,主动设置更小的内存保留行数,让更多行提前刷到磁盘:

// 只保留50行在内存,超出部分直接写入临时文件
SXSSFWorkbook wb = new SXSSFWorkbook(50);
// 如果你用的ExportRevisionResponseExcel是封装方法,一定要确保内部是这么创建SXSSFWorkbook的

默认的100行对于超大数据量来说还是偏多,调小后能显著降低内存占用。

2. 分批查询+分批写入(核心优化)

这是解决问题的关键!你现在一次性加载全量数据到List的操作,直接把SXSSF的优化逻辑架空了。改成分批查询数据库,写一批就释放一批数据:

// 假设你的transactionService支持分页查询
int pageSize = 1000; // 每批加载1000条,可根据内存情况调整
int pageNum = 1;
List<TransactionDTO> batchData;
SXSSFWorkbook wb = new SXSSFWorkbook(50);
Sheet sheet = wb.createSheet("Transaction Statistics");

// 先写入表头
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("Status");
headerRow.createCell(1).setCellValue("Request");

while ((batchData = transactionService.findByInDateRange(status, fromDate, toDate, pageNum, pageSize)) != null && !batchData.isEmpty()) {
    int startRowNum = (pageNum - 1) * pageSize + 1; // 表头占了第0行
    // 写入当前批次数据
    for (int i = 0; i < batchData.size(); i++) {
        TransactionDTO dto = batchData.get(i);
        Row row = sheet.createRow(startRowNum + i);
        row.createCell(0).setCellValue(dto.getStatus());
        row.createCell(1).setCellValue(dto.getRequest());
    }
    // 手动触发刷盘,让SXSSF把内存里的行写入临时文件
    ((SXSSFSheet)sheet).flushRows();
    // 清空当前批次,帮助GC快速回收内存
    batchData.clear();
    pageNum++;
}

这样内存里只会保留当前批次的1000条数据+SXSSF的50行窗口,内存占用会大幅下降。

3. 复用单元格样式,减少冗余对象

如果你的导出逻辑里每次创建单元格都新建样式,会产生大量重复的样式对象占用内存。提前创建好样式,全局复用:

// 提前创建好要复用的样式
CellStyle cellStyle = wb.createCellStyle();
Font font = wb.createFont();
font.setFontName("Microsoft YaHei");
font.setFontHeightInPoints((short)12);
cellStyle.setFont(font);
cellStyle.setAlignment(HorizontalAlignment.CENTER);

// 写入单元格时直接复用
row.createCell(0).setCellStyle(cellStyle);

4. 确保资源释放彻底

你已经调用了wb.dispose()和wb.close(),但最好把资源释放逻辑放到finally块里,避免异常时资源泄漏:

ByteArrayOutputStream outByteStream = null;
OutputStream outStream = null;
try {
    outByteStream = new ByteArrayOutputStream();
    wb.write(outByteStream);
    byte[] outArray = outByteStream.toByteArray();
    // 设置response的相关参数...
    outStream = response.getOutputStream();
    outStream.write(outArray);
    outStream.flush();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        if (wb != null) {
            wb.dispose(); // 释放SXSSF的临时文件
            wb.close();
        }
        if (outByteStream != null) outByteStream.close();
        if (outStream != null) outStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

5. 升级POI版本(可选)

POI 3.14是2015年的老版本,后续的4.x、5.x版本对SXSSF的内存管理做了不少优化,比如修复了一些内存泄漏问题、优化了临时文件的处理逻辑。如果项目允许,升级版本能从底层减少内存问题的概率。

总结

最核心的问题是你一次性加载了全量数据到内存,让SXSSF的磁盘缓存机制完全发挥不了作用。先改成分批查询+分批写入,再配合调小SXSSF的内存窗口,应该就能解决OOM问题了。

内容的提问来源于stack exchange,提问作者Sanatbek Matlatipov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:41:18