导出内存溢出案例分析
内存溢出排查与优化-CSDN博客
参考如上示例
分析如下代码
分页查询
protected IBean[] beforedownload(String token, Selector selector, Class<? extends IBean> clazz) {AssetFaDisposalLineExportResDTO[] resDTOS = assetFaDisposalServiceAgent.queryDetailExport(token, selector);if (ArrayUtils.isEmpty(resDTOS)) {return new IBean[0];}List<AssetFaDisposalForExport> exportList = new ArrayList<>();for (AssetFaDisposalLineExportResDTO resDTO : resDTOS) {AssetFaDisposalForExport export = new AssetFaDisposalForExport();// 头数据export.setCompanyName(resDTO.getCompanyName());export.setCompanyCode(resDTO.getCompanyCode());export.setCode(resDTO.getCode());export.setExpenseDate(resDTO.getExpenseDate());export.setDataSourceNum(resDTO.getDataSourceNum());export.setDataSource(resDTO.getDataSource());export.setPreparedBy(resDTO.getPreparedBy());export.setStatus(resDTO.getStatus());// 行数据export.setAssetNumber(resDTO.getAssetNumber());export.setTagNumber(resDTO.getTagNumber());export.setAssetName(resDTO.getAssetName());export.setAssetCategory1(resDTO.getAssetCategory1());export.setAssetCategory2(resDTO.getAssetCategory2());export.setRetiredUnits(resDTO.getRetiredUnits());export.setRetiredDate(resDTO.getRetiredDate());export.setRetirementType(resDTO.getRetirementType());export.setRetirementCost(resDTO.getRetirementCost());export.setDisposalAmount(resDTO.getDisposalAmount());export.setComment(resDTO.getComment());exportList.add(export);// MOD BY SHENKE 20250529 ED}Comparator<AssetFaDisposalForExport> companyCodeDesc = Comparator.comparing(AssetFaDisposalForExport::getCompanyCode).reversed();Comparator<AssetFaDisposalForExport> expenseDateDesc = Comparator.comparing(AssetFaDisposalForExport::getExpenseDate).reversed();Comparator<AssetFaDisposalForExport> assetNumberDesc = Comparator.comparing(AssetFaDisposalForExport::getAssetNumber).reversed();Comparator<AssetFaDisposalForExport> retiredDateDesc = Comparator.comparing(AssetFaDisposalForExport::getRetiredDate).reversed();Comparator<AssetFaDisposalForExport> finalComparator = companyCodeDesc.thenComparing(expenseDateDesc).thenComparing(assetNumberDesc).thenComparing(retiredDateDesc);return exportList.stream().sorted(finalComparator).collect(Collectors.toList()).toArray(new AssetFaDisposalForExport[0]);}
导出代码
while (true) {System.out.println(pageIndex);StopWatch stopWatch = new StopWatch();request.getData().setPageIndex(pageIndex++);request.getData().setPageSize(EXPORT_PAGESIZE);request.getData().removeOrderByField();request.getData().addOrderByField(new OrderByField("id"));stopWatch.start("查询数据" + request.getData().getPageIndex());IBean[] beans = beforedownload(request.getToken(), request.getData(), clazz);stopWatch.stop();try {stopWatch.start("写入数据" + request.getData().getPageIndex());tools.toExcel(beans, request.getParentCode(), ExcelTools.XLSXTYPE);stopWatch.stop();//greaterThanPageSizeFlag//处理 行合并到头导出if(StringUtils.isNotBlank(greaterThanPageSizeFlag)){if (beans == null || beans.length < EXPORT_PAGESIZE) {break;}}else{//beans.length != EXPORT_PAGESIZE// 处理 写sql语句查询,sql未加分页条件,只能查询一次if (beans == null || beans.length != EXPORT_PAGESIZE) {break;}}} catch (CommonException e) {throw e;} catch (Exception e) {}//System.out.println("结果" + request.getData().getPageIndex() + ":" + stopWatch.prettyPrint());}} finally {try {tools.write(response.getOutputStream());} catch (IOException e) {}tools.close();}
问题
1,
如上分页导出会内存溢出
2,
说明:正确分页每页数据1000条, List<AssetFaDisposalForExport> exportList = new ArrayList<>();list中对象什么时候回收
标准回收时机(理想情况)
-
方法执行完成后立即回收:
-
当
beforedownload
方法执行完毕时 -
如果
exportList
没有被外部引用 -
整个列表及其包含的1000个
AssetFaDisposalForExport
对象都将变为可回收状态
-
在您的分页导出场景中:
-
每页的
exportList
及其对象:会在方法执行完成后变为可回收状态 -
实际回收时间:由JVM的GC决定,通常在下次GC时回收
-
真正释放内存的关键:
-
确保没有外部长期引用返回的数据
-
3,
当前代码 IBean[] beans = beforedownload(request.getToken(), request.getData(), clazz); stopWatch.stop(); try { stopWatch.start("写入数据" + request.getData().getPageIndex()); tools.toExcel(beans, request.getParentCode(), ExcelTools.XLSXTYPE);,bean没有设置为null,会被回收吗
回收机制分析
-
方法局部变量的生命周期:
-
beans
是方法局部变量(假设在某个方法内部) -
当方法执行结束时,局部变量表会被清空
-
变量引用的对象如果没有被其他位置引用,将变为可回收状态
-
-
没有显式置null时的回收:
-
即使没有
beans = null
,Java的垃圾回收器也能正确识别不再使用的对象 -
方法执行完毕后,
beans
的栈帧引用会自动消失
-
在您的代码中:
-
没有显式设置
beans = null
不会导致内存泄漏,因为:-
方法结束后局部变量自动失效
-
除非被其他对象引用,否则会被GC回收
-
-
真正需要关注的是:
-
toExcel()
方法是否内部缓存数据 -
是否有其他静态引用持有beans中的数据
-
Excel库是否是内存友好的实现
-
4,
没有显式设置beans = null不会导致内存泄漏,因为: 方法结束后局部变量自动失效,方法结束指整个导出结束吗,while循环,需要整个导出完成,才结束吧
-
每次迭代都是独立的作用域:
-
每次循环迭代都会创建一个新的
beans
数组 -
前一次迭代的
beans
引用会在迭代结束时失效 -
但实际回收取决于:
-
Excel工具类是否缓存了数据
-
JVM的垃圾回收策略
-
-
-
关键时间点:
-
当一次循环迭代完成时:
-
当前迭代的
beans
变量已经超出作用域 -
如果没有其他引用指向这个数组,它就成为可回收对象
-
-
不需要等待整个while循环结束
-
总结
在您的分页导出代码中:
-
不需要等待整个while循环结束:每次循环迭代完成后,当次的
beans
数组就可以被回收 -
无需显式设置
beans = null
:现代JVM能很好地处理这种情况
5,回头分析内存溢出原因
内存溢出排查与优化-CSDN博客