自己挖的坑自己填--jxl進行Excel下載堆記憶體溢位問題
阿新 • • 發佈:2021-04-02
今天在進行使用 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 可以正常打