Jasper在导出时报告OutOfMemoryError

我编写了一个用于管理和运行Jasper报告的Web应用程序。 最近我一直在处理一些生成极大(1500页以上)输出的报告,并尝试解决由此产生的内存问题。 我发现了JRFileVirtualizer ,它允许我以非常有限的内存占用成功运行报表。 但是,我的应用程序的一个function是它存储以前运行的报告中的输出文件,并允许它们导出为各种格式(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本身提供的解决方案,原因很明显。

自发布此问题以来,我还向JasperSoft提交了一项function请求 。 作为后续,我被指向了JRVirtualizationHelper.setThreadVirtualizer方法。 此方法允许您设置与当前线程关联的JRVirtualizer,该线程将在JasperPrint反序列化期间使用。

我在我的项目中对此进行了测试,结果令人满意。 看起来我希望存在的function确实存在,尽管它在API中的可见性可能会得到改善。

代码示例:

 JRVirtualizer virtualizer = new JRSwapFileVirtualizer(1000, new JRSwapFile(reportFilePath, 2048, 1024), true); JRVirtualizationHelper.setThreadVirtualizer(virtualizer); 

我认为您的问题是.jrprint是一个序列化的Java对象,您必须完全反序列化。 您需要以某种方式将其分解为小文件,然后在导出时连接输出。

我的提议有点牵连,但我认为它可能有用,至少在某些情况下:

  1. 使用JRVirtualizer填写报告。 使用返回JasperPrint实例的方法,以避免将所有内容转储到巨大的.jrprint。
  2. 使用JRXmlExporter进行内部导出。 诀窍是使用适当的JRExportParameter来告诉Jasper 单独导出每个页面(您可以使用ZipOutputStream作为容器来避免包含大量文件的目录)。
  3. 如果要进行实际导出,请使用JASPER_PRINT_LIST。 重要的是列表实现是惰性的并使用JRPrintXmlLoader创建JasperPrint实例,因此您不需要一次加载整个事物。

无论如何,您应该检查Jasper源代码以检查这种方法是否可行。