多人同时导出 Excel 导致内存溢出

1、问题根因分析

        多人同时导出Excel导致内存溢出(OOM)的核心原因是:在短时间内,大量数据被加载到JVM堆内存中,且创建了大量大对象(如Apache POI的Cell、Row、Sheet对象),超过了堆内存的最大限制(-Xmx)。

  1. 同步处理与高并发:导出请求通常是同步的。当多个用户同时触发导出时,每个请求都会在服务器端创建一个处理线程,并在该线程中构建一个完整的、包含大量数据的Excel工作簿(Workbook)对象。

  2. Apache POI的内存模型:传统的Apache POI(如HSSF/XSSF)在构建Excel时,所有单元格、样式、数据都保存在内存中的Java对象里。一个几十万行的Excel文件,其对应的Workbook对象可能轻松占用几百MB甚至上GB的内存。

  3. 数据一次性加载:为了生成Excel,通常需要从数据库一次性查询出所有数据,这个巨大的ResultSet也会占用大量内存。

  4. JVM堆空间不足:如果JVM堆内存设置本身不大,或者并发导出的用户数足够多,就很容易将堆内存耗尽,触发java.lang.OutOfMemoryError: Java heap space

2、解决方案(从低代价到高代价,从临时到根本)

2.1 应用层优化(代码改造 - 最根本的解决方案)

这是最推荐的方式,从根源上解决内存问题。

a) 使用流式API (SXSSF)

Apache POI提供了专门用于处理大数据量的流式API:SXSSF。

原理

SXSSF(Streaming Usermodel API)在XSSF的基础上扩展,它只会将一部分行(例如100行)保留在内存中,生成一行,刷新一行到磁盘临时文件,从而实现低内存占用。它通过滑动窗口机制来管理内存中的行。

  • 优点:内存占用极低且恒定(仅与rowAccessWindowSize有关),是解决此问题的最佳武器。

  • 缺点:不支持一些高级特性(如公式计算、单元格合并等在刷新后可能受限),会生成临时文件。

代码示例
// 设置一个滑动窗口值,表示在内存中保留多少行,超出的行会被写入磁盘
int rowAccessWindowSize = 100;
SXSSFWorkbook workbook = new SXSSFWorkbook(rowAccessWindowSize);
SXSSFSheet sheet = workbook.createSheet("数据导出");// 写入表头
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("姓名");
headerRow.createCell(1).setCellValue("年龄");// 流式分页查询数据库并写入
int pageSize = 1000;
int pageNum = 1;
List<Data> dataList;
int currentRow = 1;do {// 1. 分页查询,避免一次性加载所有数据dataList = dataService.getExportData(pageNum, pageSize);if (dataList.isEmpty()) {break;}// 2. 将本页数据写入Excelfor (Data data : dataList) {Row row = sheet.createRow(currentRow++);row.createCell(0).setCellValue(data.getName());row.createCell(1).setCellValue(data.getAge());// ... 其他单元格}// 3. 非常重要!手动清理掉滑动窗口之外的行,释放内存// ((SXSSFSheet)sheet).flushRows() 也可以,但注意参数((SXSSFSheet) sheet).flushRows(dataList.size()); // 刷新并清理已处理的行pageNum++;
} while (!dataList.isEmpty());// 将workbook写入HttpServletResponse的输出流
workbook.write(response.getOutputStream());
workbook.dispose(); // 删除临时文件
workbook.close();

b) 分页查询数据库

如上例所示,在数据获取层,绝对不要一次性SELECT * FROM huge_table,必须使用分页查询(如MySQL的LIMIT offset, size)。这大大降低了数据库和Java应用两边的内存压力。

2.2 架构与流程优化

a) 异步导出

将同步请求改为异步任务。

  • 流程

    • 用户点击导出后,服务端立即返回一个任务ID或一个URL:“正在生成,请稍后查看下载链接”。
    • 后台使用一个独立的、线程池大小可控的任务(如使用@Async、消息队列、Job调度)来执行真正的导出操作。
    • 导出完成后,将文件上传到OSS或文件服务器,并将下载链接通过通知系统(站内信、邮件等)告知用户,或者更新任务状态供用户查询。
  • 优点
    • 避免了HTTP请求超时。

    • 可以对后台任务队列进行控流,避免同时处理过多导出任务,从而控制内存使用峰值。

    • 用户体验更好,不会因为长时间等待而导致浏览器卡死。

  • 缺点:系统设计更复杂,不能立即下载。

b) 限流与队列

如果必须同步导出,可以在应用入口进行限流。

  • 使用RateLimiter(Guava)或Sentinel等工具,限制单位时间内允许的导出请求数量。例如,最多只允许同时处理2个导出请求,后续请求排队等待或直接返回“系统繁忙,请稍后再试”。

  • 优点:简单粗暴,有效防止系统被瞬时高并发打垮。

  • 缺点:用户体验差(需要等待或失败)。

2.3 运维与配置优化(临时缓解措施)

这些不能根治问题,但可以作为一个缓冲或辅助手段。

a) 增加JVM堆内存

通过调整启动参数 -Xmx4g -Xms4g 来增大最大堆内存。

  • 优点:简单,快速。

  • 缺点

    • 只是推迟了OOM发生的时间,如果数据量或并发量持续增长,迟早还会溢出。

    • 大内存会带来更长时间的Full GC(Garbage Collection),导致应用“卡顿”。

b) 优化GC参数

针对大内存和创建大量短命对象(导出任务中的对象基本都是短命对象)的场景,使用G1垃圾收集器可能效果更好。

  • 参数示例:-XX:+UseG1GC -XX:MaxGCPauseMillis=200

c) 文件拆分与压缩

对于极端大量的数据,可以考虑不再导出单一Excel文件,而是导出多个压缩包(如每10万行一个Excel,然后打包成ZIP)。但这更多是业务逻辑的变更。

2.4 总结

  1. 立即止损(线上紧急情况)

    • 短期:如果正在频繁OOM,可以先增大堆内存 -Xmx 并重启服务,快速恢复业务。

    • 同时:在网关/应用层紧急添加导出限流策略,防止问题复发。

  2. 根本解决(中期必须完成)

    • 改造代码:将导出逻辑从使用HSSFWorkbook/XSSFWorkbook迁移到SXSSFWorkbook

    • 优化数据查询:确保数据获取是分页的,而不是一次性加载。

  3. 优化体验与架构(长期规划)

    • 改为异步导出,并提供任务查询界面。这是对用户和最系统都最友好的方式。

    • 考虑将生成的大文件存储到OSS等对象存储中,减轻应用服务器磁盘IO压力。

技术选型参考

  • 首选SXSSF + 分页查询 + 异步导出

  • 备选:如果数据模型非常简单,也可以考虑直接生成CSV文件,CSV是纯文本格式,内存开销远小于Excel。但缺点是无法处理样式和多个Sheet。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/96296.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/96296.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入 RAG(检索增强生成)系统架构:如何构建一个能查资料的大语言模型系统

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《深度探秘&#xff1a;AI界的007》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、前言 1、LLM 的局限&#xff1a;模型知识“封闭” vs 现实知识…

linux tftpboot烧写地址分析

1&#xff0c;loadaddr 是一个环境变量&#xff0c;用于指定文件&#xff08;如内核镜像、设备树等&#xff09;加载到内存的起始地址。setenv loadaddr 0x82000000setenv loadaddr 0x80008000saveenv //.保存配置将 loadaddr 设置为 0x82000000&#xff0c;表示后续文件将加载…

硬件工程师9月实战项目分享

目录 简介 人员情况 实战项目简介 功能需求 需求分析 方案设计 电源树设计 时钟树设计 主芯片外围设计 接口设计 模拟链路设计 PCB设计检查要点 测试方案设计 硬件测试培训 测试代码学习 培训目标 掌握基本的硬件设计流程 掌握以FPGA为核心的硬件设计业务知识 …

力扣刷题——59.螺旋矩阵II

力扣刷题——59.螺旋矩阵II 题目 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。示例 1&#xff1a;输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]示例 2&#xff1a; 输…

win11系统还原点恢复系统

背景 系统换位bug11后&#xff0c;真的是各种以前的操作和设置找不到&#xff0c;太烦了&#xff0c;我是没想到&#xff0c;连系统恢复还原点都这么难找。然后搜了一圈都是恢复系统之类的&#xff0c;真的崩溃。只好自己记录了。 ✍内容找到设置—>系统–>系统信息系统信…

DHCP 原理与配置(一)

应用场景随着网络规模的不断扩大&#xff0c;网络复杂度不断提升&#xff0c;网络中的终端设备例如主机、手机、 平板等&#xff0c;位置经常变化。终端设备访问网络时需要配置IP地址、网关地址、DNS服务器 地址等。采用手工方式为终端配置这些参数非常低效且不够灵活。 IETF于…

SARibbon的编译构建及详细用法

目录 1.1 源码构建 1.2 搭建项目 1.3 详细用法 1.4 不同风格 1.5 完整代码 引言:SARibbon是一个专门为Qt框架设计的开源Ribbon风格界面控件库,它模仿了微软Office和WPS的Ribbon UI风格,适用于需要复杂菜单和工具栏的大型桌面程序。本文从源码编译构建到详细使用,做了一…

CSS【详解】性能优化

精简 CSS移除未使用的 CSS&#xff08;“死代码”&#xff09;&#xff0c;可借助工具如 PurgeCSS、UnCSS 自动检测并删除未被页面使用的样式。避免重复样式&#xff0c;通过提取公共样式&#xff08;如 mixin 或公共类&#xff09;减少代码冗余。利用预处理器&#xff08;Sass…

Flutter 线程模型详解:主线程、异步与 Isolate

一、主线程&#xff1a;默认的执行环境 所有代码默认运行在主线程。下面的例子展示了一个会阻塞主线程的错误示范&#xff1a; import package:flutter/material.dart;void main() {runApp(const MyApp()); }class MyApp extends StatelessWidget {const MyApp({super.key});ov…

ChartDB:可视化数据库设计工具私有化部署

ChartDB:可视化数据库设计工具私有化部署一、什么是ChartDB ChartDB 是一款基于 Web 的开源数据库可视化工具&#xff0c;专为简化数据库设计与管理流程而开发。以下是其核心特性与功能概述: 1、核心功能 智能查询可视化‌&#xff1a;通过单条 SQL 查询即可生成数据库架构图&a…

单片机-FreeRTOS(ing)

目录 一、基础介绍 1.1 调度策略 1.1.1 调度方式 1.1.2 调度器 1.2 任务以及优先级 1.2.1 任务与协程 1.2.2 任务状态 1.2.3 任务优先级 1.2.4 任务优先级分配方案 1.3 任务间通信 - 信号量 1.3.1 信号量 1.3.2 任务间计数信号量的实现 1.3.3 中断方式计数信号量的…

为什么调用API总返回404,该如何调试

当调用一个应用程序接口&#xff08;API&#xff09;时&#xff0c;持续地收到“404 未找到”的错误&#xff0c;其核心原因在于客户端发起的“请求”&#xff0c;未能成功地&#xff0c;匹配到服务器上任何一个“真实存在”的、可供访问的“资源路径”。这本质上&#xff0c;是…

医疗信息化自主可控转型的实践探索 —— 以常德二院为例

目录 头雁领航 - 激发医疗新质生产力 核心支撑 - 电科金仓奠定数据底座 生态共建 - 携手护航医疗信创发展 信创产业发展是国家经济数字化转型、提升产业链发展的关键&#xff0c;是科技自立自强的核心基座&#xff0c;其本质是实现中国信息化产业的自主可控。医疗信创作为关…

Gin传参和接收参数的方式

Gin查询参数和接收参数的方式 常用 Gin 绑定方法对比方法用途特点c.Bind()自动识别 Content-Type最通用&#xff0c;根据请求头自动选择绑定方式c.ShouldBindJSON()只绑定 JSON强制使用 JSON 格式&#xff0c;类型明确c.ShouldBindXML()只绑定 XML强制使用 XML 格式c.ShouldBin…

MariaDB/MySQL 客户端工具与服务端配置精要指南

文章目录一、客户端与服务端程序二、用户账号管理三、MySQL 客户端命令3.1 命令类型​3.2 使用模式​3.3 常用选项​3.4 提示符定制​四、mysqladmin管理命令​​五、服务端配置​5.1 配置文件​​​5.2 Socket 通信配置​​六、最佳实践总结免费个人运维知识库&#xff0c;欢迎…

自动化项目日报生成工具测评与选型:如何匹配团队日报管理需求

引言在项目管理场景中&#xff0c;手动撰写日报常面临多重效率瓶颈&#xff1a;任务数据分散在协作群、Excel 表格、项目看板等多个平台&#xff0c;汇总时需反复核对&#xff1b;不同成员日报格式不统一&#xff0c;管理层整合分析耗时&#xff1b;任务进度与日报信息不同步&a…

基于SpringBoot+Vue的吴韵苏香文旅小程序(协同过滤算法、Echarts图形化分析、腾讯地图API、二维码识别)

&#x1f388;系统亮点&#xff1a;协同过滤算法、Echarts图形化分析、腾讯地图API、二维码识别&#xff1b;一.系统开发工具与环境搭建1.系统设计开发工具后端使用Java编程语言的Spring boot框架 项目架构&#xff1a;B/S架构 运行环境&#xff1a;win10/win11、jdk17小程序&a…

python numpy.random的基础教程(附opencv 图片转数组、数组转图片)

目录 1.在区间[a,b)随机生成n个整数。 2.在区间[a,b)随机生成n个数。 3.在区间[0,1)生成随机数 4.打乱顺序 5.从指定的列表中选择 NumPy&#xff08;Numerical Python&#xff09;是一个开源的科学计算库&#xff0c;专门用于高效处理多维数组&#xff08;ndarray&#xf…

Vue2.x核心技术与实战(二)

目录 三、Vue2.x:生命周期+工程化开发(组件入门) 3.1 生命周期 3.1.1 生命周期 & 生命周期四个阶段 3.1.2 生命周期钩子 Vue生命周期钩子案例 - 新闻列表 & 输入框自动聚焦 3.2 综合案例:小黑记账清单 3.3 工程化开发入门 3.3.1 工程化开发 & 脚手架Vue …

【鸿蒙心迹】7×24小时极限求生:当Origin_null遇上鸿蒙,我如何用100杯咖啡换一条跨域活路?

文章概要 大家好&#xff0c;我是那个把黑眼圈熬成华为工牌挂绳的倒霉蛋。过去100个夜晚&#xff0c;我在HarmonyOS NEXT的ArkWeb里被Origin:null反复按在地上摩擦——小程序白屏、OPTIONS 400、官方文档沉默三连击。最终&#xff0c;我用C、libcurl、OpenSSL和一堆速溶咖啡&am…