写入CSV文件致JVM崩溃,千万级报表生成场景优化方案咨询
优化1000万条记录CSV导出的内存问题方案
哇,一次性加载1000万条Java对象再写CSV,服务器不崩才怪!这绝对是内存瓶颈的典型场景,我给你分享几个在实际项目里跑通的优化思路,核心就是避免一次性把所有数据塞进内存:
1. 数据库层面开启流式查询,分批取数
不要直接执行SELECT * FROM table把所有数据拉到内存里,改用流式/分页的方式获取数据:
- 用JDBC的话,可以给
Statement设置statement.setFetchSize(Integer.MIN_VALUE),让数据库驱动分批返回结果集,而不是一次性加载所有数据到内存。 - 用JPA/Spring Data的话,返回
Stream<T>类型(比如Spring Data的@Query方法返回Stream<User>),或者使用ScrollableResults滚动读取结果,每次只加载少量数据。
2. 配合Super CSV的逐行写入,边读边写
Super CSV本身支持逐行写入,完全不需要缓存所有对象。结合上面的流式查询,实现**“读一条,写一条”**的流水线:
// 伪代码示例 try (ICsvBeanWriter writer = new CsvBeanWriter(new FileWriter("report.csv"), CsvPreference.STANDARD_PREFERENCE); Stream<DataRecord> dataStream = dataRepository.streamAllRecords()) { // 流式查询 writer.writeHeader(DataRecord.class); for (DataRecord record : dataStream) { writer.write(record, DataRecord.class); // 逐行写入,内存仅存当前对象 } }
这样堆内存里永远只有当前处理的几条数据,内存占用会降到极低。
3. 跳过Java对象转换,直接从ResultSet写CSV
如果你的业务不需要把数据库字段转换成Java对象,可以直接用Super CSV的CsvResultSetWriter,把ResultSet的内容直接写入CSV,省掉对象实例化的内存开销:
try (CsvResultSetWriter writer = new CsvResultSetWriter(new FileWriter("report.csv"), CsvPreference.STANDARD_PREFERENCE); ResultSet rs = statement.executeQuery("SELECT * FROM large_table")) { writer.write(rs); // 直接从ResultSet写数据,无需中转对象 }
这会进一步降低内存占用,效率也更高。
4. 邮件附件的内存优化
生成CSV后发送邮件时,不要把整个文件读进内存再作为附件发送,用FileDataSource直接关联文件:
MimeBodyPart attachmentPart = new MimeBodyPart(); attachmentPart.setDataHandler(new DataHandler(new FileDataSource("report.csv"))); attachmentPart.setFileName("report.csv");
如果文件超大,还可以先把CSV压缩成ZIP包再发送,既减少文件体积,也降低传输时的内存压力。
5. 异步处理,避免阻塞主线程
把导出任务放到异步线程或者任务队列里(比如Spring的@Async、Quartz),这样用户发起请求后不用一直等待,服务器也不会因为长时间的导出任务占用核心线程影响其他业务。导出完成后再给用户发邮件通知即可。
另外你提到的SXSSFWorkbook是POI处理大Excel的方案,它通过临时文件缓存超过内存阈值的行来避免溢出,但CSV的结构比Excel简单得多,完全不需要这么重的方案,流式读写就足够解决问题了。
内容的提问来源于stack exchange,提问作者Ramesh
相关产品推荐
相关产品推荐

