zoukankan      html  css  js  c++  java
  • 自己挖的坑自己填--jxl进行Excel下载堆内存溢出问题

      今天在进行使用 jxl 进行 Excel 下载时,由于数据量大(4万多条接近5万条数据的下载),数据结构过于负责,存在大量大对象(虽然在对象每次用完都设置为null,但还是存在内存溢出问题),加上本地电脑内存不大(只有8G),导致下载数据时报堆内存溢出,下载失败。

    Exception data: java.lang.OutOfMemoryError
    at jxl.write.biff.MemoryDataOutput.write(MemoryDataOutput.java:72)
    at jxl.write.biff.File.write(File.java:149)
    at jxl.write.biff.RowRecord.writeCells(RowRecord.java:324)
    at jxl.write.biff.SheetWriter.write(SheetWriter.java:479)
    at jxl.write.biff.WritableSheetImpl.write(WritableSheetImpl.java:1431)
    at jxl.write.biff.WritableWorkbookImpl.write(WritableWorkbookImpl.java:915)

      下面是案例复现的简单模拟代码:

            <dependency>
                <groupId>net.sourceforge.jexcelapi</groupId >
                <artifactId>jxl</artifactId >
                <version>2.6.12</version >
            </dependency >
    public class MyExcel {
        public static void main(String[] args) throws Exception {
            File xlsFile = new File("E:\jxl.xls");
            // 创建一个工作簿
            WritableWorkbook workbook = Workbook.createWorkbook(xlsFile);
            // 创建一个工作表
            WritableSheet sheet = workbook.createSheet("sheet1", 0);
            Map<Integer, ArrayList<Map<Integer, Person>>> mapListMap = new HashMap<>();
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBatisConfig.class);
            PersonServiceImpl bean = context.getBean(PersonServiceImpl.class);
            //获取数据库数据
            List<Person> listBean = bean.getList();
            //进行数据处理
            for (int row = 0; row < 45000; row++) {
                ArrayList<Map<Integer, Person>> listMap = new ArrayList<Map<Integer, Person>>();
                for (int col = 0; col < 30; col++) {
                    Map<Integer, Person> map = new HashMap<Integer, Person>();
                    for (int j = 0; j < listBean.size(); j++) {
                        map.put(j, listBean.get(j));
                    }
                    listMap.add(map);
                    map = null;
                }
                mapListMap.put(row, listMap);
                listMap = null;
            }
            //写数据
            for (int row = 0; row < 45000; row++) {
                ArrayList<Map<Integer, Person>> listMap = mapListMap.get(row);
                for (int col = 0; col < 30; col++) {
                    Map<Integer, Person> map = listMap.get(col);
                    for (int j = 0; j < listBean.size(); j++) {
                        Person person = map.get(j);
                        sheet.addCell(new Label(col, row, person.getId()+"" + row + col));
                        sheet.addCell(new Label(col, row, person.getName() + row + col));
                        sheet.addCell(new Label(col, row, person.getAge()+"" + row + col));
                        map = null;
                    }
                }
                listMap = null;
            }
            workbook.write();
            workbook.close();
        }

       运行后结果:


      解决办法:

      (1)最简单的方法是加大内存,本地电脑内存过小,当把代码部署到公司测试环境(测试环境内存是16G)时该问题不再复现;

      (2)采用临时文件写入EXCEL功能,设定临时文件的位置,可以有效的避免内存溢出(jxl版本必须是2.6.12及其以上,2.6版本没有采用临时文件的功能);

    public static void main(String[] args) throws Exception {
            File xlsFile = new File("E:\jxl.xls");
            // 创建一个工作簿
            WorkbookSettings ws = new WorkbookSettings();
            ws.setUseTemporaryFileDuringWrite(true);
            ws.setTemporaryFileDuringWriteDirectory(new File("E:\"));//指定一个临时文件路径
            WritableWorkbook workbook = Workbook.createWorkbook(xlsFile,ws);
            // 创建一个工作表
            WritableSheet sheet = workbook.createSheet("sheet1", 0);
            Map<Integer, ArrayList<Map<Integer, Person>>> mapListMap = new HashMap<>();
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBatisConfig.class);
            PersonServiceImpl bean = context.getBean(PersonServiceImpl.class);
            //获取数据库数据
            List<Person> listBean = bean.getList();
            //进行数据处理
            for (int row = 0; row < 45000; row++) {
                ArrayList<Map<Integer, Person>> listMap = new ArrayList<Map<Integer, Person>>();
                for (int col = 0; col < 30; col++) {
                    Map<Integer, Person> map = new HashMap<Integer, Person>();
                    for (int j = 0; j < listBean.size(); j++) {
                        map.put(j, listBean.get(j));
                    }
                    listMap.add(map);
                    map = null;
                }
                mapListMap.put(row, listMap);
                listMap = null;
            }
            //写数据
            for (int row = 0; row < 45000; row++) {
                ArrayList<Map<Integer, Person>> listMap = mapListMap.get(row);
                for (int col = 0; col < 30; col++) {
                    Map<Integer, Person> map = listMap.get(col);
                    for (int j = 0; j < listBean.size(); j++) {
                        Person person = map.get(j);
                        sheet.addCell(new Label(col, row, person.getId()+"" + row + col));
                        sheet.addCell(new Label(col, row, person.getName() + row + col));
                        sheet.addCell(new Label(col, row, person.getAge()+"" + row + col));
                    }
              map = null; } listMap
    = null; } workbook.write(); workbook.close(); }

       当开始执行下载时,会产生一个临时文件,jxl 会把数据写入到这个临时文件中,写入结束后会把这个临时文件删除:

      经过 jconsole.exe 监控可以看出:使用了临时文件后多了一个线程进行类的卸载,使用了临时文件的线程高峰有15个,类卸载了6个,没有使用临时文件时线程数一直保持14个到报内存溢出结束,0个类卸载:

      PS:

      (1)Excel 表一个 sheet 页可以存储6万条,所以一般超过5万条的,其他的数据就存储其他sheet中;或采用分成几个 Excel 表来实现数据下载;

      (2)今天测试下载的 Excel 数据文本有170M(数据有4.8w条),通过MS Office 打开失败,为空 Excel 表,但通过 WPS 可以正常打开。

    作者:huangrenhui
    欢迎任何形式的转载,但请务必注明出处。
    如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】。
    如果,您希望更容易地发现我的新博客,不妨点击一下左下角的【关注我】。
    如果,您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是【码猿手】。
    限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
  • 相关阅读:
    PHP ftp_nb_continue() 函数
    PHP ftp_mkdir() 函数
    PHP ftp_mdtm() 函数
    普通索引和唯一索引,应该怎么选择
    [学习笔记]拉格朗日中值定理
    asp dotnet core 通过图片统计 csdn 用户访问
    WPF 使用 SharpDx 异步渲染
    WPF 使用 SharpDx 异步渲染
    win10 uwp 解决 SerialDevice.FromIdAsync 返回空
    win10 uwp 解决 SerialDevice.FromIdAsync 返回空
  • 原文地址:https://www.cnblogs.com/huangrenhui/p/14608463.html
Copyright © 2011-2022 走看看