3

首先让我声明我根本不知道 XSLT。我的任务是调查在 XSLT 处理期间发生的 Java OutOfMemory 异常的一些 JVM 转储。

我发现 OutOfMemory 发生在递归 XSLT 处理期间(我们使用 XALAN)。

我发现令人震惊的是递归深度超过 100 000 次。

在什么情况下可以接受 XSLT 处理期间如此深的递归?


请注意,线程堆栈跟踪大约有 300k 行长,并且在 OutOfMemory 发生之前充满了这种变化:

at org/apache/xalan/transformer/TransformerImpl.executeChildTemplates(Bytecode PC:150(Compiled Code)) at org/apache/xalan/templates/ElemElement.execute(Bytecode PC:352(Compiled Code)) at org/apache/xalan/transformer/TransformerImpl.executeChildTemplates(Bytecode PC:150(Compiled Code))

4

2 回答 2

9

当使用原始递归处理非常长的序列时,可能会发生这种情况。

想象一下sum()使用递归命名模板实现函数:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
  <xsl:call-template name="sum">
   <xsl:with-param name="pSeq" select="/*/*"/>
  </xsl:call-template>
 </xsl:template>

 <xsl:template name="sum">
  <xsl:param name="pAccum" select="0"/>
  <xsl:param name="pSeq"/>

  <xsl:choose>
   <xsl:when test="not($pSeq)">
     <xsl:value-of select="$pAccum"/>
   </xsl:when>
   <xsl:otherwise>
    <xsl:call-template name="sum">
     <xsl:with-param name="pAccum"
          select="$pAccum+$pSeq[1]"/>
     <xsl:with-param name="pSeq"
          select="$pSeq[position() >1]"/>
    </xsl:call-template>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

应用于以下 XML 文档时:

<nums>
  <num>01</num>
  <num>02</num>
  <num>03</num>
  <num>04</num>
  <num>05</num>
  <num>06</num>
  <num>07</num>
  <num>08</num>
  <num>09</num>
  <num>10</num>
</nums>

结果是

55

现在,假设nums有 1000000 (1M)num个孩子。这将是寻找一百万个数字的总和的合理尝试,但是大多数 XSLT 处理器通常在递归深度为 1000 或大约 1000 时崩溃。

解决方案

  1. 使用尾递归(一种特殊的递归,其中递归调用是模板中的最后一条指令)。一些 XSLT 处理器识别尾递归并在内部对其进行优化以进行迭代,因此没有递归和堆栈溢出。

  2. 使用 DVC 风格的递归(分而治之)。这适用于所有 XSLT 处理器。最大递归深度为 log2(N),对于大多数实际目的是可行的。例如,处理 1M 个项目的序列只需要 19 的堆栈深度。

这是 sum 模板的 DVC 实现:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
  <xsl:call-template name="sum">
   <xsl:with-param name="pSeq" select="/*/*"/>
  </xsl:call-template>
 </xsl:template>

 <xsl:template name="sum">
  <xsl:param name="pSeq"/>

  <xsl:variable name="vCnt" select="count($pSeq)"/>

  <xsl:choose>
   <xsl:when test="$vCnt = 0">
     <xsl:value-of select="0"/>
   </xsl:when>
   <xsl:when test="$vCnt = 1">
     <xsl:value-of select="$pSeq[1]"/>
   </xsl:when>
   <xsl:otherwise>
    <xsl:variable name="vHalf" select=
     "floor($vCnt div 2)"/>

    <xsl:variable name="vSum1">
     <xsl:call-template name="sum">
      <xsl:with-param name="pSeq" select=
      "$pSeq[not(position() > $vHalf)]"/>
     </xsl:call-template>
    </xsl:variable>

    <xsl:variable name="vSum2">
     <xsl:call-template name="sum">
      <xsl:with-param name="pSeq" select=
      "$pSeq[position() > $vHalf]"/>
     </xsl:call-template>
    </xsl:variable>

    <xsl:value-of select="$vSum1+$vSum2"/>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

使用此模板查找一百万个数字的总和需要一些时间,但会产生正确的结果而不会崩溃。

于 2011-03-26T17:43:15.933 回答
0

这很可能是导致无限递归的 XSLT 中的错误(其中“无限”被定义为“直到内存不足”)。考虑以下模板:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <xsl:apply-templates select="/"/>
    </xsl:template>
</xsl:stylesheet>

文档中的 onlytemplate匹配根元素,然后调用apply-templates自身,这将启动一个永远不会终止的进程。

于 2011-03-25T17:36:18.073 回答