4

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

4

3 回答 3

5

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

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

代码示例:

JRVirtualizer virtualizer = new JRSwapFileVirtualizer(1000, new JRSwapFile(reportFilePath, 2048, 1024), true);
JRVirtualizationHelper.setThreadVirtualizer(virtualizer);
于 2011-09-28T14:52:32.770 回答
2

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

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

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

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

于 2011-09-26T22:19:30.763 回答
0

谢谢你的问题和你自己的答案。

但我对您的解决方案还有一个问题:

您说您使用该方法JRVirtualizationHelper.setThreadVirtualizer设置JRSwapFileVirtualizer与当前线程关联的实例。但是由于您的要求是将一些以前生成的报告导出到 PDF/CSV 文件中,我认为 GENERATE 和 EXPORT 操作在两个单独的线程中运行,因为这两个操作可能是由两个单独的用户单击生成的。

JRSwapFileVirtualizer那么,为什么可以为两个线程设置一个实例呢?您使用的是单服务器 JVM?

于 2022-02-11T04:01:47.547 回答