2011-09-26 106 views
3

我编写了一个用于管理和运行Jasper报告的Web应用程序。最近,我一直在处理一些报告,这些报告会生成非常大的(1500+页)输出,并试图解决由此产生的内存问题。我发现了JRFileVirtualizer,这让我能够以非常有限的内存占用情况成功运行报告。但是,我的应用程序的一个特点是它存储以前运行的报告的输出文件,并允许将它们导出为各种格式(PDF,CSV等)。因此,我发现自己处于拥有500 + MB .jrprint文件并希望将其导出为例如CSV的需求的情况。下面是一些简单的例子代码:Jasper在导出时报告OutOfMemoryError

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_PRINT参数),最终都会调用JRLoader.loadObject(...),这会尝试将我的整个500MB报告加载到内存中(请参阅net.sf.jasperreports.engine.JRAbstractExporter.setInput())。

我的问题是,有没有办法解决这个问题? 500MB是可行的,但它并没有离开我的应用程序非常面向未来的,并报告执行JRVirtualizer溶液离开我希望有将用于出口类似的东西。我愿意让自己的手变脏,并扩展一些Jasper内部类,但理想的解决方案将由Jasper自己提供,理由很明显。

+0

1500+页的报告,这是如此之大,它是无用的。也许考虑打破这些报告。 –

+0

不幸的是,这是一个使用通用工具出于许多不同目的的情况。此报告(CSV格式)通过脚本导入到另一个数据库中,而不是由人类浏览。 – Eric

+0

我已经用JasperSoft提交了一个功能请求,因为它看起来像项目预算约束会阻止我寻求像下面这样建议的更复杂的解决方案。 http://jasperforge.org/projects/jasperreports/tracker/view.php?id=5478 – Eric

回答

4

自发布此问题以来,我也提交了JasperSoft feature request。作为后续行动,我指出了JRVirtualizationHelper.setThreadVirtualizer方法。此方法允许您设置与当前线程关联的JRVirtualizer,这将在JasperPrint反序列化过程中使用。

我已经在我的项目中测试过了,效果令人满意。看起来我希望存在的功能确实存在,尽管它在API中的可见性可能有所改进。

代码示例:

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

你需要调用clearThreadVirtualizer(),还是自动清理某处? –

2

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

我的建议是参与了一点,但我认为它可能工作,至少在某些情况下:

  1. 使用JRVirtualizer填写您的报告。使用返回JasperPrint实例的方法,以避免将所有内容转储为巨大的.jrprint。
  2. 使用JRXmlExporter执行内部导出。诀窍是使用合适的JRExportParameter s告诉Jasper分别导出每个页面(您可以使用ZipOutputStream作为容器以避免包含大量文件的目录)。
  3. 当您想要进行真正的导出时,请使用JASPER_PRINT_LIST。重要的是,该列表实现懒惰并创建一个使用JRPrintXmlLoaderJasperPrint实例之一,所以你并不需要一次加载整个事情。

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

+0

感谢您的建议。我认为你在反序列化的过程中击中了头部。我想从Jasper看到的是在反序列化/导出期间提供JRVirtualizer的选项,以便自动执行此操作。我会沿着这条线做一些实验,看看我能想出什么。 – Eric

+0

@Eric在反序列化过程中,'JRVirtualizer'对Jasper来说是很多工作:基本上它意味着使用'Externalizable'或自定义机制来序列化'.jrprint',而不是基于自动的Java'Serializable'。虽然虚拟化反序列化看起来是正确的选择,但我怀疑他们会更容易为我的方法的一些变体提供内置支持,提供一页一页的自动导出到一个zip和一个懒惰列表实现。 – gpeche