我正在使用 jasper 报告生成一个巨大的 Excel 报告,该报告可能会扩展到 100k+ 行。
我首先得到我的报告行,然后将它们提供给 jasperReport 对象。这样做没有问题。
fillReport 方法需要一些时间,但似乎也做得很好。
但是在导出我的报告时,我的内存使用量很大,最终我的 GC 超载,我的应用程序崩溃了。作为记录,这是我得到的例外:
java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.Arrays.copyOf(Unknown Source)
at java.io.ByteArrayOutputStream.grow(Unknown Source)
at java.io.ByteArrayOutputStream.ensureCapacity(Unknown Source)
at java.io.ByteArrayOutputStream.write(Unknown Source)
at java.io.ObjectOutputStream$BlockDataOutputStream.write(Unknown Source)
at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
at java.io.ObjectOutputStream.defaultWriteObject(Unknown Source)
at net.sf.jasperreports.engine.fill.JRTemplatePrintElement.writeObject(JRTemplatePrintElement.java:363)
at sun.reflect.GeneratedMethodAccessor359.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at java.io.ObjectStreamClass.invokeWriteObject(Unknown Source)
at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at java.util.ArrayList.writeObject(Unknown Source)
at sun.reflect.GeneratedMethodAccessor154.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at java.io.ObjectStreamClass.invokeWriteObject(Unknown Source)
at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at net.sf.jasperreports.engine.fill.JRAbstractLRUVirtualizer.writeData(JRAbstractLRUVirtualizer.java:704)
at net.sf.jasperreports.engine.fill.JRSwapFileVirtualizer.pageOut(JRSwapFileVirtualizer.java:87)
at net.sf.jasperreports.engine.fill.JRAbstractLRUVirtualizer.virtualizeData(JRAbstractLRUVirtualizer.java:664)
我想知道是否有办法优化这种内存使用并防止这种崩溃,特别是当我需要能够让我的用户同时生成这些报告时。我曾尝试使用交换文件和虚拟器,但这似乎只是在优化填充报告过程。
这是我调用报告生成函数的代码:
List<LineaInformeFacturacionExcelCVO> listaLineasInformeFacturacionExcelCVO = new ArrayList<LineaInformeFacturacionExcelCVO>();
List<LineaInformeFacturacionExcel> lineasInformeFacturacionExcel = lineaInformeFacturacionExcelDao.getLineasInformeFacturacionExcel(filtros);
getBytesInformeExcel(reportRealPath, parameters, listaLineasInformeFacturacionExcelCVO, true, jrExporterParameters);
这是函数本身:
public byte[] getBytesInformeExcel(String reportRealPath, Map<String, Object> parameters, List<?> list, boolean virtualize, Map<JRExporterParameter, Object> jrExporterParameters) throws EmptyReportException, JRException {
if (list != null && list.isEmpty()) {
throw new EmptyReportException();
}
// Acordarse de pasarlo entre subrepors
if (parameters == null)
parameters = new HashMap<String, Object>();
Locale spainLocale = new Locale("es","ES");
TimeZone europeMadridTimezone = TimeZone.getTimeZone("Europe/Madrid");
parameters.put(JRParameter.REPORT_LOCALE, spainLocale);
parameters.put(JRParameter.REPORT_TIME_ZONE, europeMadridTimezone);
JRSwapFileVirtualizer virtualizer = null;
if (virtualize) {
virtualizer = new JRSwapFileVirtualizer(250, new JRSwapFile(System.getProperty("java.io.tmpdir"), 250, 250));
parameters.put(JRParameter.REPORT_VIRTUALIZER, virtualizer);
}
parameters.put(JRParameter.IS_IGNORE_PAGINATION, false);
JasperReport jasperReport;
byte[] reportBytes = null;
JasperPrint jasperPrint = null;
try {
jasperReport = (JasperReport) JRLoader.loadObjectFromFile(reportRealPath);
Crono crono = new Crono();
crono.startCrono();
LOG.info("Iniciando JasperFillManager.fillReport() ");
if (list == null) {
Connection connection = lineaInformeFacturacionDao.obtenerConnection();
jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, connection);
}
else {
jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, new JRBeanCollectionDataSource(list));
}
LOG.info("Fin JasperFillManager.fillReport() :" + crono.stopCrono() + " ms.");
// Comprueba si el report está vacío.
if (jasperPrint.getPages().isEmpty() || jasperPrint.getPages().get(0).getElements().isEmpty()) {
throw new EmptyReportException();
}
ByteArrayOutputStream xlsReport = new ByteArrayOutputStream();
JRExporter exporter = new JRXlsExporter();;
String formato = parametrosConfiguracion.getPropertie("informes.excel.formato");
if(StringUtils.isNotBlank(formato)) {
if(formato.equals(parametrosConfiguracion.getPropertie("excel.formato.xls"))) {
exporter = new JRXlsExporter();
} else if(formato.equals(parametrosConfiguracion.getPropertie("excel.formato.xlsx"))) {
exporter = new JRXlsxExporter();
}
}
exporter.setParameter(JRXlsExporterParameter.JASPER_PRINT, jasperPrint);
exporter.setParameter(JRXlsExporterParameter.OUTPUT_STREAM, xlsReport);
// Carga los parámetros por defecto
exporter.setParameter(JRXlsExporterParameter.IS_ONE_PAGE_PER_SHEET, Boolean.FALSE);
exporter.setParameter(JRXlsExporterParameter.IS_DETECT_CELL_TYPE, Boolean.TRUE);
exporter.setParameter(JRXlsExporterParameter.IS_WHITE_PAGE_BACKGROUND, Boolean.FALSE);
exporter.setParameter(JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS, Boolean.TRUE);
// Carga estos parámetros pasados como parámetro sobreescribiendo los por defecto
if (jrExporterParameters != null) {
exporter.setParameters(jrExporterParameters);
exporter.setParameter(JRXlsExporterParameter.JASPER_PRINT, jasperPrint);
exporter.setParameter(JRXlsExporterParameter.OUTPUT_STREAM, xlsReport);
}
crono = new Crono();
crono.startCrono();
LOG.info("Iniciando exporter.exportReport() ");
exporter.exportReport();
LOG.info("Fin exporter.exportReport() :" + crono.stopCrono() + " ms.");
reportBytes = xlsReport.toByteArray();
}finally{
if (virtualize) {
virtualizer.cleanup();
}
}
return reportBytes;
}
最后,如果这有任何帮助,这里是生成此报告时内存分析程序的屏幕截图:
崩溃发生在第二个 GC 使用高峰。
直接导出到文件
exporter.setParameter(JRXlsExporterParameter.OUTPUT_FILE, new File("c://archivo.xlsx"));
而不是 ByteStream 会产生以下异常:
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
at java.io.ByteArrayOutputStream.grow(Unknown Source)
at java.io.ByteArrayOutputStream.ensureCapacity(Unknown Source)
at java.io.ByteArrayOutputStream.write(Unknown Source)
at java.io.ObjectOutputStream$BlockDataOutputStream.drain(Unknown Source)
at java.io.ObjectOutputStream$BlockDataOutputStream.setBlockDataMode(Unknown Source)
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at net.sf.jasperreports.engine.fill.JRAbstractLRUVirtualizer.writeData(JRAbstractLRUVirtualizer.java:704)
at net.sf.jasperreports.engine.fill.JRSwapFileVirtualizer.pageOut(JRSwapFileVirtualizer.java:87)
at net.sf.jasperreports.engine.fill.JRAbstractLRUVirtualizer.virtualizeData(JRAbstractLRUVirtualizer.java:664)
at net.sf.jasperreports.engine.fill.JRAbstractLRUVirtualizer.evict(JRAbstractLRUVirtualizer.java:485)
at net.sf.jasperreports.engine.fill.JRAbstractLRUVirtualizer.requestData(JRAbstractLRUVirtualizer.java:630)
at net.sf.jasperreports.engine.base.ElementsBlock.ensureData(VirtualizableElementList.java:463)
at net.sf.jasperreports.engine.base.ElementsBlock.ensureDataAndTouch(VirtualizableElementList.java:432)
at net.sf.jasperreports.engine.base.ElementsBlock.get(VirtualizableElementList.java:283)
at net.sf.jasperreports.engine.base.ElementsBlockList.get(VirtualizableElementList.java:717)
at net.sf.jasperreports.engine.base.VirtualizableElementList.get(VirtualizableElementList.java:96)
at net.sf.jasperreports.engine.base.VirtualizableElementList.get(VirtualizableElementList.java:54)
at net.sf.jasperreports.engine.export.JRGridLayout.createWrappers(JRGridLayout.java:922)
at net.sf.jasperreports.engine.export.JRGridLayout.<init>(JRGridLayout.java:140)
at net.sf.jasperreports.engine.export.JRXlsAbstractExporter.exportPage(JRXlsAbstractExporter.java:1000)
at net.sf.jasperreports.engine.export.JRXlsAbstractExporter.exportReportToStream(JRXlsAbstractExporter.java:983)
at net.sf.jasperreports.engine.export.JRXlsAbstractExporter.exportReport(JRXlsAbstractExporter.java:650)
at com.services.informes.InformesService.getBytesInformeExcel(InformesService.java:920)
at com.services.informes.InformesService.getBytesInformeFacturacionExcel(InformesService.java:277)
at com.controllers.InformesController.exportarInformeFacturacionExcel(InformesController.java:391)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:212)
这是这种情况下的内存分析: