生成pdf文档时如何解决java.lang.OutOfMemoryError:Java堆空间?

时间:2019-07-04 09:47:47

标签: java out-of-memory heap-memory apache-fop

我的系统在处理一个大文件时抛出异常:“ java.lang.OutOfMemoryError:Java堆空间”。我意识到StringWriter.toString()会导致堆中的大小增加一倍,因此可能会导致此问题。我如何优化下面的代码块以避免内存不足。

public byte[] generateFromFo(final StringWriter foString) {
        try {
            StringReader foReader = new StringReader(foString.toString());
            ByteArrayOutputStream pdfWriter = new ByteArrayOutputStream();
            Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, fopFactory.newFOUserAgent(),pdfWriter);
            TRANSFORMER_FACTORY.newTransformer().transform(new StreamSource(foReader), new SAXResult(fop.getDefaultHandler()));
            LOG.debug("Completed rendering PDF output!");
            return pdfWriter.toByteArray();
        } catch (Exception e) {
            LOG.error("Error while generating PDF from FO",e);
            throw new AuditReportExportServiceException(AuditErrorCode.INTERNAL_ERROR,"Could not generate PDF from XSL-FO");
        }
    }

2 个答案:

答案 0 :(得分:2)

使用字节的InputStream可能会将foString的内存减少多达2倍(char = 2个字节)。

ByteArrayOutputStream在填充期间会调整大小,因此添加估计的需求会加快处理速度,并且可能会导致调整大小过多。

        InputStream foReader = new ByteArrayInputStream(
                foString.toString().getBytes(StandardCharsets.UTF_8);
        foString.close();
        final int initialCapacity = 160 * 1024;
        ByteArrayOutputStream pdfWriter = new ByteArrayOutputStream(initialCapacity);
        Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, fopFactory.newFOUserAgent(),
            pdfWriter);
        TRANSFORMER_FACTORY.newTransformer().transform(new StreamSource(foReader),
            new SAXResult(fop.getDefaultHandler()));

最好是更改API:

public void generateFromFo(final String foString, OutputStream pdfOut) { ... }

这可能会使ByteArrayOutputStream变得多余,并且您可能会立即流式传输到文件,URL或其他任何内容。

文档本身和生成的PDF也存在问题:

  • 图像尺寸(但请记住打印分辨率更高)
  • 一些图像可以很好地矢量化
  • 重复的图像(如页面标题中的图像)应存储一次
  • 理想情况下,字体应该是标准字体,是(使用过的字符)第二好的嵌入式子集。
  • XML可能不是最佳选择,非常重复

答案 1 :(得分:1)

广泛地,您有两个主要选择:

  1. 增加进程可用的内存。 Java的-Xmx选项将设置此配置。您可以通过例如-Xmx8G要求64位系统上有8GB的内存(如果有的话)。文件在这里:http://docs.oracle.com/javase/7/docs/technotes/tools/windows/java.html#nonstandard

  2. 将代码更改为以较小的块“流式传输”数据,而不是像在此所做的那样尝试将整个文件组装到内存中的byte[]中。您可以将变压器的输出更改为FileOutputStream而不是ByteArrayOutputStream,并在显示的代码中返回File而不是byte[]吗?或者,根据您对该方法的输出进行处理,您可以返回InputStream并允许使用者以流方式接收文件数据吗?

    您可能还需要进行更改,以便以流方式使用此方法的输入。如何执行此操作取决于如何创建StringWriter foString的详细信息。您可能需要将OutputStream“管道”到InputStream中才能完成这项工作,请参见https://docs.oracle.com/javase/7/docs/api/java/io/PipedInputStream.html

1更简单。这里2可能更好。