You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache FOP 2.2生成超大量记录PDF时遇OutOfMemoryError求助

我帮你梳理下解决Apache FOP处理75K+记录时OutOfMemoryError: Java heap space的方案,重点从XSL-FO模板优化和XML结构调整入手,这些方法能有效降低内存占用,支持大记录量的PDF生成:

一、XSL-FO模板核心优化

1. 拆分大型Page Sequence为多个独立序列

你之前尝试增加page-sequence数量有效果,但还不够彻底——要把每个记录组(比如IncompletedRecords、ApprovedRecords等)拆分为单独的page-sequence。这样FOP处理完一个序列后会立即释放该序列的内存,不会把所有75K记录都滞留在内存中。

同时提取通用表格模板,减少代码冗余的同时让内存管理更高效:

<xsl:template match="Report">
  <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
    <fo:layout-master-set>
      <fo:simple-page-master master-name="page" page-height="11in" page-width="8.5in" margin-left="0.45in" margin-right="0.6in" margin-top="0.25in" margin-bottom="0.25in">
        <fo:region-body region-name="xsl-region-body" />
        <fo:region-before region-name="xsl-region-before" />
        <fo:region-after region-name="xsl-region-after" />
      </fo:simple-page-master>
    </fo:layout-master-set>

    <!-- 单独的Header页序列 -->
    <fo:page-sequence master-reference="page">
      <fo:static-content flow-name="xsl-region-after">
        <fo:block text-align="end" font-size="10pt" font-family="serif" line-height="14pt">
          Page <fo:page-number /> of <fo:page-number-citation-last ref-id="final-sequence" />
        </fo:block>
      </fo:static-content>
      <fo:flow flow-name="xsl-region-body">
        <xsl:apply-templates select="Header" />
      </fo:flow>
    </fo:page-sequence>

    <!-- 处理未完成记录组 -->
    <fo:page-sequence master-reference="page">
      <fo:static-content flow-name="xsl-region-after">
        <fo:block text-align="end" font-size="10pt" font-family="serif" line-height="14pt">
          Page <fo:page-number /> of <fo:page-number-citation-last ref-id="final-sequence" />
        </fo:block>
      </fo:static-content>
      <fo:flow flow-name="xsl-region-body">
        <fo:block font-size="12pt" font-weight="bold" margin-bottom="10pt">Incompleted Records</fo:block>
        <xsl:call-template name="record-table">
          <xsl:with-param name="records" select="Records/IncompletedRecords/IncompleteRec" />
        </xsl:call-template>
      </fo:flow>
    </fo:page-sequence>

    <!-- 处理已批准记录组 -->
    <fo:page-sequence master-reference="page">
      <fo:static-content flow-name="xsl-region-after">
        <fo:block text-align="end" font-size="10pt" font-family="serif" line-height="14pt">
          Page <fo:page-number /> of <fo:page-number-citation-last ref-id="final-sequence" />
        </fo:block>
      </fo:static-content>
      <fo:flow flow-name="xsl-region-body">
        <fo:block font-size="12pt" font-weight="bold" margin-bottom="10pt">Approved Records</fo:block>
        <xsl:call-template name="record-table">
          <xsl:with-param name="records" select="Records/ApprovedRecords/ApprovedRec" />
        </xsl:call-template>
      </fo:flow>
    </fo:page-sequence>

    <!-- 其他记录组(Deleted/Pending/Deactivated/Rejected)依葫芦画瓢 -->
    
    <!-- 最后一个页序列,用于全局总页码计算 -->
    <fo:page-sequence master-reference="page" id="final-sequence">
      <fo:static-content flow-name="xsl-region-after">
        <fo:block text-align="end" font-size="10pt" font-family="serif" line-height="14pt">
          Page <fo:page-number /> of <fo:page-number-citation-last ref-id="final-sequence" />
        </fo:block>
      </fo:static-content>
      <fo:flow flow-name="xsl-region-body">
        <fo:block font-size="12pt" font-weight="bold" margin-bottom="10pt">Rejected Records</fo:block>
        <xsl:call-template name="record-table">
          <xsl:with-param name="records" select="Records/RejectedRecords/RejectedRec" />
        </xsl:call-template>
      </fo:flow>
    </fo:page-sequence>
  </fo:root>
</xsl:template>

<!-- 通用记录表格模板 -->
<xsl:template name="record-table">
  <xsl:param name="records" />
  <fo:block font-size="8pt">
    <fo:table table-layout="fixed" width="100%" border-collapse="collapse">
      <fo:table-column column-width="1.5in" />
      <fo:table-column column-width="2.5in" />
      <fo:table-column column-width="1.5in" />
      <fo:table-column column-width="2in" />
      <fo:table-header border-bottom="1pt solid black">
        <fo:table-cell padding-left="3pt" background-color="lightgrey" height="0.25in" display-align="center">
          <fo:block margin-bottom="1mm" font-weight="bold">First Name</fo:block>
        </fo:table-cell>
        <fo:table-cell background-color="lightgrey" height="0.25in" display-align="center">
          <fo:block margin-bottom="1mm" font-weight="bold">Last Name</fo:block>
        </fo:table-cell>
        <fo:table-cell background-color="lightgrey" height="0.25in" display-align="center">
          <fo:block margin-bottom="1mm" font-weight="bold">Address</fo:block>
        </fo:table-cell>
        <fo:table-cell background-color="lightgrey" height="0.25in" display-align="center">
          <fo:block margin-bottom="1mm" font-weight="bold">Last Updated</fo:block>
        </fo:table-cell>
      </fo:table-header>
      <fo:table-body keep-together.within-page="false">
        <xsl:for-each select="$records">
          <fo:table-row>
            <fo:table-cell padding-left="3pt" border-bottom="0.5pt solid #ccc">
              <fo:block><xsl:value-of select="FName" /></fo:block>
            </fo:table-cell>
            <fo:table-cell border-bottom="0.5pt solid #ccc">
              <fo:block><xsl:value-of select="LName" /></fo:block>
            </fo:table-cell>
            <fo:table-cell border-bottom="0.5pt solid #ccc">
              <fo:block><xsl:value-of select="Address" /></fo:block>
            </fo:table-cell>
            <fo:table-cell border-bottom="0.5pt solid #ccc">
              <fo:block><xsl:value-of select="LastUpdDate" /></fo:block>
            </fo:table-cell>
          </fo:table-row>
        </xsl:for-each>
      </fo:table-body>
    </fo:table>
  </fo:block>
</xsl:template>

2. 减少内存开销的细节优化

  • 坚持使用table-layout="fixed":避免FOP为计算自动布局加载所有单元格内容,节省大量内存。
  • 禁用全局总页码(可选):如果不需要整个PDF的总页码,把每个page-sequence的页码改为独立计数(比如Page X of Y仅针对当前记录组),这样FOP不用跟踪所有页面的元数据。
  • 强制表格分页:在<fo:table-body>添加keep-together.within-page="false",允许FOP在表格中间分页,避免把整个大表格加载到内存等待分页。
二、XML结构调整策略

如果75K记录的XML文件体积过大(几十MB甚至更大),可以从源头上降低内存压力:

  • 分块生成XML:不要一次性生成包含所有记录的XML,按记录组拆分多个XML文件,分别生成PDF后,用PDFBox或iText等工具合并成最终文件。每个小XML的内存占用极低,FOP处理起来毫无压力。
  • 流式XML处理:如果使用支持XSLT 3.0的处理器(比如Saxon-EE),开启流式处理模式(<xsl:mode streamable="yes">),边读取XML边生成FO内容,无需把整个XML加载到内存中。
三、辅助优化(配合模板调整)

虽然你重点问模板和XML修改,但配合JVM参数能进一步提升稳定性:

  • 增加堆内存:启动FOP时添加-Xmx4G(根据机器配置调整,比如8G内存可设-Xmx6G)
  • 使用G1垃圾收集器:添加-XX:+UseG1GC,更高效地回收内存,适合大内存场景

内容的提问来源于stack exchange,提问作者Civitas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:14:05