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
相关产品推荐
相关产品推荐

