一、问题描述:
公司之前的项目中客户有一个需求是将业务数据导出到Excel表中,方便他们对账,单个导出任务数据量近100W,每当月初任务量多时,导出的项目就会内存溢出,挂掉。

二、原因分析:
1、每个进程在写Excel文件时,都是先将数据加载到内存,然后再将内存里面的数据生成文件;因此单个进程任务的数据量过大,将无法及时回收系统内存,最终导致系统内存耗尽而宕机。
2、导出中oracle查询结果是一次性全部查询出来,占用大量系统内存资源。

三、优化方案思路:
1、将所有导出查询全部改成分页的方式查询;
2、将写Excel文件使用java的基础技术IO流来实现,采用POI拼接xml字符串完成,迭代一批数据就flush进硬盘,同时把list,大对象赋值为空,显式调用垃圾回收器,及时回收内存。

四、具体优化改造方案:
1、改造导出查询方法:
这里省略,数据分页查询及SQL优化请自行度娘,这里不深入分析;
2、工程中增加生成Excel文件实现类

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFDataFormat;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import com.chengfeng.ne.global.service.ITaskService;
import com.thinkjf.core.config.GlobalConfig;/*** 功能描述:生成Excel文件类* @author Jeff* @version 1.0* @date 2015-08-03*/
@Service("xlsxOutPutService")
public class XlsxOutPutService {@Autowiredprivate ITaskService taskService;/*** 导出每个sheet行数*/public int pageSize = Integer.parseInt(GlobalConfig.getPropertyValue("common.exoprt.Worksheet.max.rownum"));/*** 根据传入的不同serviceName来执行不同的查询语句* @param serviceName* @param execMethod* @param params* @param pageIndex* @return*/public List<?> queryBySerivceName(String serviceName,String execMethod, Map<String, Object> params,int pageIndex)throws Exception{List<?> resultList = null;if("taskService".equals(serviceName)){resultList = taskService.queryExportResultPage(execMethod,params, pageIndex, pageSize);}return resultList;}/*** 生成Excel文件外部调用方法* @param headList 标题列表* @param fieldName 字段列表* @param sheetName 工作薄sheet名称* @param tempFilePath 临时文件目录* @param filePath 目标文件* @param execMethod 执行sql* @param params 查询参数* @param serviceName 执行service方法对象名称* @throws Exception*/public void generateExcel(List<String> headList,List<String> fieldName,String sheetName, String tempFilePath,String filePath,String execMethod, Map<String, Object> params,String serviceName)throws Exception {XSSFWorkbook wb = new XSSFWorkbook();Map<String, XSSFCellStyle> styles = createStyles(wb);XSSFSheet sheet = wb.createSheet(sheetName);String sheetRef = sheet.getPackagePart().getPartName().getName();  String sheetRefList = sheetRef.substring(1);   File tempFiledir = new File(tempFilePath);if(!tempFiledir.exists()){tempFiledir.mkdirs();}String uuid = UUID.randomUUID().toString();uuid = uuid.replace("-", "");File sheetFileList = new File(tempFilePath + "/sheet_" + uuid + ".xml");File tmpFile = new File(tempFilePath + "/"+uuid+".xlsx");FileOutputStream os = new FileOutputStream(tmpFile);wb.write(os);os.close();Writer fw = new OutputStreamWriter(new FileOutputStream(sheetFileList), "UTF-8");//生成sheetgenerateExcelSheet(headList,fieldName, fw, styles,execMethod,params,serviceName);fw.close();//将临时文件压缩替换FileOutputStream out = new FileOutputStream(filePath);substituteAll(tmpFile, sheetFileList, sheetRefList, out);out.close();// 删除临时文件tmpFile.delete();sheetFileList.delete();tmpFile = null;sheetFileList = null;os = null;fw = null;out = null;Runtime.getRuntime().gc();}/*** 生成sheet* @param headList* @param fields* @param out* @param styles* @param execMethod* @param params* @throws Exception*/private void generateExcelSheet(List<String> headList,List<String> fields,Writer out,Map<String, XSSFCellStyle> styles,String execMethod, Map<String, Object> params,String serviceName) throws Exception {XSSFCellStyle stringStyle = styles.get("cell_string");XSSFCellStyle longStyle = styles.get("cell_long");XSSFCellStyle doubleStyle = styles.get("cell_double");XSSFCellStyle dateStyle = styles.get("cell_date");Calendar calendar = Calendar.getInstance();SpreadsheetWriter sw = new SpreadsheetWriter(out);sw.beginWorkSheet();sw.beginSetColWidth();for (int i = 10, len = headList.size() - 2; i < len; i++) {sw.setColWidthBeforeSheet(i, 13);}sw.setColWidthBeforeSheet(headList.size() - 1, 16);sw.endSetColWidth();sw.beginSheet();// 表头sw.insertRowWithheight(0, headList.size(), 25);int styleIndex = ((XSSFCellStyle) styles.get("sheet_title")).getIndex();for (int i = 0, len = headList.size(); i < len; i++) {sw.createCell(i, headList.get(i), styleIndex);}sw.endWithheight();//int pageIndex = 1;// 查询起始页Boolean isEnd = false;// 是否是最后一页,循环条件do {// 开始分页查询// 导出查询改为分页查询方式,替代原有queryExportResult()方法long startTimne = System.currentTimeMillis();List<?> dataList = this.queryBySerivceName(serviceName, execMethod, params, pageIndex);long endTime = System.currentTimeMillis();System.out.println("查询"+pageIndex+"完成用时="+((endTime-startTimne))+"毫秒");if (dataList != null && dataList.size() > 0) {//写方法-------int cellIndex = 0;for (int rownum = 1, len = dataList.size() + 1; rownum < len; rownum++) {cellIndex = 0;sw.insertRow((pageIndex-1)*pageSize+rownum);Object data = dataList.get(rownum-1);Object val = null;Method fieldMethod = null;for (int k = 0, len2 = fields.size(); k < len2; k++) {fieldMethod = (Method) data.getClass().getMethod("get"+ fields.get(k));fieldMethod.setAccessible(true);// 不进行安全检测val = fieldMethod.invoke(data);if(val == null){sw.createCell(cellIndex,"",stringStyle.getIndex());}else{String typeName = fieldMethod.getGenericReturnType().toString();if (typeName.endsWith("int") || typeName.endsWith("nteger")) {sw.createCell(cellIndex, (Integer) val,longStyle.getIndex());} else if (typeName.endsWith("ong")) {sw.createCell(cellIndex, (Long) val, longStyle.getIndex());} else if (typeName.endsWith("ouble")) {sw.createCell(cellIndex, (Double) val,doubleStyle.getIndex());} else if (typeName.endsWith("util.Date")) {calendar.setTime((java.util.Date) val);sw.createCell(cellIndex, calendar, dateStyle.getIndex());} else if (typeName.endsWith("sql.Date")) {calendar.setTime((java.sql.Date) val);sw.createCell(cellIndex, calendar, dateStyle.getIndex());} else {sw.createCell(cellIndex, val==null?"":val.toString().replace("<", "&lt;").replace(">", "&gt;"),stringStyle.getIndex());}}cellIndex++;}sw.endRow();if (rownum % 2000 == 0) {out.flush();}}               //------------                              isEnd = true;pageIndex++;} else {isEnd = false; }dataList = null;Runtime.getRuntime().gc();} while (isEnd);sw.endSheet();// 合并单元格
//      sw.beginMergerCell();
//      for (int i = 0, len = dataList.size() + 1; i < len; i++) {//        sw.setMergeCell(i, 8, i, 9);
//      }
//      sw.endMergerCell();sw.endWorkSheet();}/*** 创建Excel样式* @param wb* @return*/private static Map<String, XSSFCellStyle> createStyles(XSSFWorkbook wb) {Map<String, XSSFCellStyle> stylesMap = new HashMap<String, XSSFCellStyle>();XSSFDataFormat fmt = wb.createDataFormat();XSSFCellStyle style = wb.createCellStyle();style.setAlignment(XSSFCellStyle.ALIGN_CENTER);style.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);stylesMap.put("cell_string", style);XSSFCellStyle style2 = wb.createCellStyle();style2.setDataFormat(fmt.getFormat("0"));style2.setAlignment(XSSFCellStyle.ALIGN_CENTER);style2.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);stylesMap.put("cell_long", style2);XSSFCellStyle style3 = wb.createCellStyle();style3.setDataFormat(fmt.getFormat("0.00"));style3.setAlignment(XSSFCellStyle.ALIGN_CENTER);style3.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);stylesMap.put("cell_double", style3);XSSFCellStyle style4 = wb.createCellStyle();style4.setDataFormat(fmt.getFormat("yyyy-MM-dd HH:mm:ss"));style4.setAlignment(XSSFCellStyle.ALIGN_CENTER);style4.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);stylesMap.put("cell_date", style4);XSSFCellStyle style5 = wb.createCellStyle();style5.setFillForegroundColor(IndexedColors.AQUA.getIndex());style5.setFillPattern(XSSFCellStyle.SOLID_FOREGROUND);style5.setAlignment(XSSFCellStyle.ALIGN_CENTER);style5.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);stylesMap.put("sheet_title", style5);return stylesMap;}/*** 打包压缩* @param zipfile* @param tmpfileList* @param entryList* @param out* @throws IOException*/private void substituteAll(File zipfile,File tmpfileList,String entryList, OutputStream out) throws IOException {ZipFile zip = new ZipFile(zipfile);ZipOutputStream zos = new ZipOutputStream(out);@SuppressWarnings("unchecked")Enumeration<ZipEntry> en = (Enumeration<ZipEntry>)zip.entries();while (en.hasMoreElements()) {ZipEntry ze = en.nextElement();if (!entryList.contains(ze.getName())) {zos.putNextEntry(new ZipEntry(ze.getName()));InputStream is = zip.getInputStream(ze);copyStream(is, zos);is.close();is = null;System.gc();}}InputStream is = null;zos.putNextEntry(new ZipEntry(entryList));is = new FileInputStream(tmpfileList);copyStream(is, zos);is.close();zos.close();zip.close();is = null;zos = null;zip = null; System.gc();}private static void copyStream(InputStream in, OutputStream out)throws IOException {byte[] chunk = new byte[1024*10];int count;while ((count = in.read(chunk)) >= 0)out.write(chunk, 0, count);}public int getTrueColumnNum(String address) {address = address.replaceAll("[^a-zA-Z]", "").toLowerCase();char[] adds = address.toCharArray();int base = 1;int total = 0;for (int i = adds.length - 1; i >= 0; i--) {total += (adds[i] - 'a' + 1) * base;base = 26 * base;}return total;}public static class SpreadsheetWriter {private final Writer _out;private int _rownum;public SpreadsheetWriter(Writer out) {this._out = out;}public void beginWorkSheet() throws IOException {this._out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?><worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">");}public void beginSheet() throws IOException {this._out.write("<sheetData>\n");}public void endSheet() throws IOException {this._out.write("</sheetData>");// 合并单元格}public void endWorkSheet() throws IOException {this._out.write("</worksheet>");}//插入行 不带高度public void insertRow(int rownum) throws IOException {this._out.write("<row r=\"" + (rownum + 1) + "\">\n");this._rownum = rownum;}public void endRow() throws IOException {this._out.write("</row>\n");}//插入行且设置高度public void insertRowWithheight(int rownum, int columnNum, double height)throws IOException {this._out.write("<row r=\"" + (rownum + 1) + "\" spans=\"1:"+ columnNum + "\" ht=\"" + height+ "\" customHeight=\"1\">\n");this._rownum = rownum;}public void endWithheight() throws IOException {this._out.write("</row>\n");}public void beginSetColWidth() throws IOException {this._out.write("<cols>\n");}// 设置列宽 下标从0开始public void setColWidthBeforeSheet(int columnIndex, double columnWidth)throws IOException {this._out.write("<col min=\"" + (columnIndex + 1) + "\" max=\""+ (columnIndex + 1) + "\" width=\"" + columnWidth+ "\" customWidth=\"1\"/>\n");}public void endSetColWidth() throws IOException {this._out.write("</cols>\n");}public void beginMergerCell() throws IOException {this._out.write("<mergeCells>\n");}public void endMergerCell() throws IOException {this._out.write("</mergeCells>\n");}// 合并单元格 下标从0开始public void setMergeCell(int beginColumn, int beginCell, int endColumn,int endCell) throws IOException {this._out.write("<mergeCell ref=\"" + getExcelName(beginCell + 1)+ (beginColumn + 1) + ":" + getExcelName(endCell + 1)+ (endColumn + 1) + "\"/>\n");// 列行:列行}public void createCell(int columnIndex, String value, int styleIndex)throws IOException {String ref = new CellReference(this._rownum, columnIndex).formatAsString();this._out.write("<c r=\"" + ref + "\" t=\"inlineStr\"");if (styleIndex != -1)this._out.write(" s=\"" + styleIndex + "\"");this._out.write(">");this._out.write("<is><t>" + value + "</t></is>");this._out.write("</c>");}public void createCell(int columnIndex, String value)throws IOException {createCell(columnIndex, value, -1);}public void createCell(int columnIndex, double value, int styleIndex)throws IOException {String ref = new CellReference(this._rownum, columnIndex).formatAsString();this._out.write("<c r=\"" + ref + "\" t=\"n\"");if (styleIndex != -1)this._out.write(" s=\"" + styleIndex + "\"");this._out.write(">");this._out.write("<v>" + value + "</v>");this._out.write("</c>");}public void createCell(int columnIndex, double value)throws IOException {createCell(columnIndex, value, -1);}public void createCell(int columnIndex, Calendar value, int styleIndex)throws IOException {createCell(columnIndex, DateUtil.getExcelDate(value, false),styleIndex);}//10 进制转26进制private String getExcelName(int i) {char[] allChar = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();StringBuilder sb = new StringBuilder();while (i > 0) {sb.append(allChar[i % 26 - 1]);i /= 26;}return sb.reverse().toString();}}}

在外部类调用时:

        String tempFilePath = GlobalConfig.getPropertyValue("common.attach.upload_dir") + "/task/tmp/";//调用新的生成方法      xlsxOutPutService.generateExcel(Arrays.asList(cellName), fieldName,MessageUtils.getMessage(exportDateType.toString()),tempFilePath, expFilePath, execMethod, params, "taskService");

五、性能测试
1、测试一:多线程写文件
描述:22个线程,都同时导出35个字段, 35万数据,耗时16分钟,每个文件48M

2、测试二:多线程写文件
描述:10个线程,都同时导出35个字段, 75万数据,耗时16分钟,每个文件102M

以上测试没有再报内存溢出的问题了,时间有点慢,主要是时间大部是被查询给占用掉了,项目里面的查询性能还有待优化。

大数据导出Excel导致内存溢出的解决方案相关推荐

  1. 解决POI大数据导出Excel内存溢出、应用假死

    最近公司一个06年统计项目在导出Excel时造成应用服务器内存溢出.假死现象:查看代码发现问题一次查询一整年的数据导致堆内存被撑爆(假死),随后改用批量查询往Excel中写数据,同样的问题又出现了!! ...

  2. php查询mysql返回大量数据结果集导致内存溢出的解决方法

    web开发中如果遇到php查询mysql返回大量数据导致内存溢出.或者内存不够用的情况那就需要看下MySQL C API的关联,那么究竟是什么导致php查询mysql返回大量数据时内存不够用情况? 答 ...

  3. 大数据导出excel大小限制_大数据量导出Excel的方案

    测试共同条件: 数据总数为110011条,每条数据条数为19个字段. 电脑配置为:P4 2.67GHz,1G内存. 一.POI.JXL.FastExcel比较 POI.JXL.FastExcel均为j ...

  4. 大数据导出excel大小限制_EXCEL大数据量导出的解决方案

    将web页面上显示的报表导出到excel文件里是一种很常见的需求.润乾报表的类excel模型,支持excel文件数据无失真的导入导出,使用起来非常的方便.然而,当数据量较大的情况下,excel本身的支 ...

  5. POI3.8解决导出大数据量excel文件时内存溢出的问题

    POI3.8解决导出大数据量excel文件时内存溢出的问题 参考文章: (1)POI3.8解决导出大数据量excel文件时内存溢出的问题 (2)https://www.cnblogs.com/feng ...

  6. POI读写超大数据量Excel,解决超过几万行而导致内存溢出的问题(附源码)

    来源:cnblogs.com/swordfall/p/8298386.html 1. Excel2003与Excel2007 两个版本的最大行数和列数不同,2003版最大行数是65536行,最大列数是 ...

  7. POI实现大数据EXCLE导入导出,解决内存溢出问题

    POI实现大数据EXCLE导入导出,解决内存溢出问题 参考文章: (1)POI实现大数据EXCLE导入导出,解决内存溢出问题 (2)https://www.cnblogs.com/huangjian2 ...

  8. 接口数据量太大,导致内存溢出,解决办法

    通常我们使用接口调用数据总是返回一段我们需要的信息,或者是json 格式信息,通过接收将数据保存到程序当中,再对接收到的数据进行转换成对应的模型格式 .目前遇到的问题是接收的数据量过于巨大,导致完整接 ...

  9. POI百万级大数据量EXCEL导出

    一. 简介 excel导出,如果数据量在百万级,会出现俩点内存溢出的问题: 1. 查询数据量过大,导致内存溢出. 该问题可以通过分批查询来解决: 2. 最后下载的时候大EXCEL转换的输出流内存溢出: ...

最新文章

  1. 信通院2018人工智能发展白皮书技术篇重磅发布
  2. mysql 主从报错
  3. 使用Palette类提取图片的颜色信息
  4. 网站特效-------旋转的图片
  5. 窥探SnowflakeIdWorker之并发生成唯一ID
  6. 免费课程 | 云脑机器学习实战训练营,中美大咖携手带你飞!
  7. 经典DL论文研读(part4)--ImageNet Classification with Deep Convolutional Neural Networks
  8. Qt文档阅读笔记-The Meta-Object System解析及实例
  9. oracle 修改序列末值,当ViewModel值更改时,用户界面未更新
  10. 台前与幕后的 5G 战争
  11. Android修改高度,android – 如何在运行时更改软键盘的高度?
  12. 零基础学python鱼c-《零基础入门学习Python》第二版和第一版的区别在哪里呢?...
  13. ubuntu linux软件,Linux新系统必装软件(Ubuntu及类似系统)
  14. 01互联网三高架构的演进之道
  15. 《算法笔记》9.7 堆
  16. 尼日利亚4g频段_世界各国全球主要4g频段资料
  17. 蓝精灵协会 (The Smurfs‘ Society) 宣布与著名艺术家展开一系列的合作,打造传奇 PFP 系列
  18. PostgreSQL的学习心得和知识总结(五十三)|语法级自上而下完美实现MySQL数据库的 insert set 的实现方案
  19. unity优化—资源优化
  20. python 节点关系图_python 可视化节点关系(一):networkx

热门文章

  1. 开票货物名称写计算机配件可以吗,如何添加开票货物名称
  2. (二)理解word2vec:实践篇
  3. 路漫漫其修远兮···VB 来15个数尝尝咸淡
  4. Git连接oschina管理代码版本
  5. 2年Java开发工作经验面试总结
  6. 这可能是你近 2 年发论文最好机会!
  7. 基于深度学习的人脸识别与管理系统(UI界面增强版,Python代码)
  8. 【案例分析】地产集团公司BI项目建设方案
  9. Android 开发之Okhttp网络请求日志打印
  10. 马氏快速记星座法——5分钟快速记忆星座和月份!超快!超准!超实用!