【问题标题】:Jasper Reports OutOfMemoryError on exportJasper 在导出时报告 OutOfMemoryError
【发布时间】:2011-11-25 12:09:13
【问题描述】:

我编写了一个用于管理和运行 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 自己提供的解决方案。

【问题讨论】:

  • 1500+ 页报告,太大了没用。也许考虑分解这些报告。
  • 不幸的是,这是将通用工具用于许多不同目的的情况。此报告(CSV 格式)通过脚本导入另一个数据库,而不是人类浏览。
  • 我已经向 JasperSoft 提交了功能请求,因为项目预算限制将阻止我寻求更精细的解决方案,例如下面 gpeche 建议的解决方案。 jasperforge.org/projects/jasperreports/tracker/view.php?id=5478

标签: java jasper-reports out-of-memory export-to-csv


【解决方案1】:

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

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

代码示例:

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

【讨论】:

  • 是否需要调用clearThreadVirtualizer(),还是在某处自动清理?
【解决方案2】:

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

我的建议有点牵强,但我认为它可能会奏效,至少在某些情况下:

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

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

【讨论】:

  • 感谢您的建议。我认为你在反序列化中一针见血。我希望从 Jasper 看到的是在反序列化/导出期间提供 JRVirtualizer 以便自动执行此操作的选项。我将沿着这条路线做一些实验,看看我能想出什么。
  • @Eric A JRVirtualizer 在反序列化期间对 Jasper 来说是很多工作:基本上它意味着使用 Externalizable 或自定义机制而不是 Java Serializable 序列化 .jrprints,这基本上是自动的。虽然虚拟化反序列化看起来是正确的选择,但我怀疑他们为我的方法的某些变体提供内置支持会更容易,提供逐页自动导出到 zip 和惰性列表实现。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-04
  • 2018-07-07
  • 1970-01-01
  • 2019-06-20
  • 1970-01-01
  • 2016-03-29
  • 1970-01-01
相关资源
最近更新 更多