自己挖的坑自己填--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 可以正常打開。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章