我编写了一个用于管理和运行 Jasper 报告的 Web 应用程序。最近我一直在处理一些生成非常大(1500 多页)输出的报告,并试图解决由此产生的内存问题。我发现了JRFileVirtualizer
,这使我能够以非常有限的内存占用成功运行报告。但是,我的应用程序的功能之一是它存储以前运行的报告的输出文件,并允许将它们导出为各种格式(PDF、CSV 等)。因此,我发现自己有一个 500+MB 的 .jrprint 文件,并希望将其导出到,例如,按需 CSV。这是一些简化的示例代码:
JRCsvExporter exporter = new JRCsvExporter();
exporter.setParameter(JRExporterParameter.INPUT_FILE_NAME, jrprintPath);
exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, outputStream);
exporter.exportReport();
不幸的是,当我在我提到的大文件上尝试这个时,我得到一个OutOfMemoryError
:
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.io.ObjectInputStream$HandleTable.grow(ObjectInputStream.java:3421)
at java.io.ObjectInputStream$HandleTable.assign(ObjectInputStream.java:3227)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1744)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
at java.util.ArrayList.readObject(ArrayList.java:593)
at sun.reflect.GeneratedMethodAccessor184.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:974)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1849)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1753)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
at net.sf.jasperreports.engine.base.JRVirtualPrintPage.readObject(JRVirtualPrintPage.java:423)
...
从浏览 Jasper 的一些内部结构来看,无论我如何尝试设置此导出(我也尝试过JASPER_PRINT
直接加载和设置参数),最终都会有一个调用JRLoader.loadObject(...)
将尝试加载我的整个 500MB 报告进入内存(参见net.sf.jasperreports.engine.JRAbstractExporter.setInput()
)。
我的问题是,有没有办法解决这个问题,而不仅仅是在问题上扔内存?500MB 是可行的,但它不会让我的应用程序非常适应未来,JRVirtualizer
报告执行的解决方案让我希望导出类似的东西。我愿意亲自动手并扩展一些 Jasper 内部类,但出于显而易见的原因,理想的解决方案是 Jasper 自己提供的解决方案。