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

二次调用@Async处理Excel方法时出现Java堆内存溢出问题

问题分析与解决方案

首先,不需要手动清理堆内存,Java的GC会自动管理内存,但你的代码存在资源未正确释放和潜在内存泄漏的问题,导致第二次调用时堆内存耗尽。以下是具体问题和修复方案:

1. 核心问题:POI资源未关闭

你的代码中OPCPackage没有关闭,这是POI处理Excel时的重量级对象,会占用大量堆内存。即使你关闭了文件流和sheet流,OPCPackage不关闭的话,内部的内存映射、缓存数据不会被释放;尤其@Async方法在独立线程执行,这些对象会被线程持有,GC无法及时回收。

2. 具体修复步骤

(1)用try-with-resources自动管理资源

将OPCPackage、sheet流等资源放在try-with-resources块中,确保代码执行完毕或异常时自动关闭:

@Async
// 移除@Transactional:@Async方法在独立线程执行,事务上下文不会传递,该注解无效
public void load(InputStream file, boolean save, boolean deleteYear) throws IOException {
    try (OPCPackage pkg = OPCPackage.open(file); // 自动关闭OPCPackage
         InputStream sheet2 = new XSSFReader(pkg).getSheet("rId1")) { // 自动关闭sheet流
        SharedStringsTable sst = new XSSFReader(pkg).getSharedStringsTable();
        XMLReader parser = fetchSheetParser(sst);
        InputSource sheetSource = new InputSource(sheet2);
        parser.parse(sheetSource);

        SheetHandlerService sheetHandlerService = (SheetHandlerService) parser.getContentHandler();
        LOG.info("end processing data");
        
        if (deleteYear) { 
            deleteTable();
        }
        
        if (save) {
            saveCorrect();
            saveErrors();
        }
        // 清理Handler中的缓存数据
        sheetHandlerService.clearCachedData(); // 假设Handler有这个方法,清空内部集合
    } catch (Exception e) {
        LOG.error("处理Excel失败", e); // 打印完整堆栈,不要只打消息
    }
}

(2)移除无效的@Transactional注解

@Async方法会在Spring创建的异步线程池中执行,和原请求线程的事务上下文完全隔离,所以你的@Transactional注解根本不会生效,反而可能导致不必要的线程资源浪费,直接移除即可。

(3)清理SheetHandlerService的缓存

如果你的SheetHandlerService在解析Excel时缓存了大量行数据(比如用List存储所有解析结果),处理完saveCorrect()和saveErrors()后,必须手动清空这些缓存集合,让GC能回收这部分内存:

// 在SheetHandlerService中添加清理方法
public void clearCachedData() {
    if (this.correctData != null) {
        this.correctData.clear();
        this.correctData = null; // 可选,帮助GC更快回收
    }
    if (this.errorData != null) {
        this.errorData.clear();
        this.errorData = null;
    }
}

(4)配置合理的@Async线程池

默认的Spring异步线程池是无界的(SimpleAsyncTaskExecutor),如果短时间内多次调用,会创建大量线程,每个线程持有POI资源,快速耗尽内存。建议自定义线程池:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4); // 根据CPU核心数设置
        executor.setMaxPoolSize(8);
        executor.setQueueCapacity(10); // 任务队列大小,避免无界队列
        executor.setThreadNamePrefix("ExcelProcessor-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

(5)优化POI解析的内存占用

如果Excel文件极大,即使SAX解析也可能因为缓存数据过多导致内存问题,可以:

  • 在SheetHandlerService中分批保存数据,解析一批就保存一批,不要缓存全部数据
  • 禁用POI的不必要缓存,比如设置POIXMLDocument.setCacheMemoryPackage(false)

3. 额外排查点

  • 检查saveCorrect()和saveErrors()方法是否存在内存泄漏(比如未关闭数据库连接、缓存了大量查询结果)
  • 监控堆内存使用(用jmap、jvisualvm工具),确认内存泄漏的对象类型,精准定位问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 01:54:14