【问题标题】:XSLT processing recursion depthXSLT 处理递归深度
【发布时间】:2011-07-23 02:16:41
【问题描述】:

首先让我声明我根本不知道 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))

【问题讨论】:

  • 可以创建一个导致无限递归的转换。你能发布你的模板吗?
  • 这就是为什么这被称为stackoverflow...
  • 好问题,+1。有关递归处理导致堆栈溢出的原因以及该问题的两种解决方案的详细说明,请参阅我的答案。

标签: java xslt recursion xalan


【解决方案1】:

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

想象一下使用递归命名模板实现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>

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

【讨论】:

  • +1,这就是我要找的。导致深度递归的合法处理。再说一次,没有任何 XSLT 经验:你会说如果一个人开始遇到 StackOverflow 异常,是时候研究和优化模板了吗? (而不是将 -Xss 增加到一个可笑的巨大数字(如 700megs)来绕过它们?)
  • 之前问了一些奇怪的问题,然后无法跟进并验证自己就是这样。
【解决方案2】:

这很可能是 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>

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

【讨论】:

    猜你喜欢
    • 2015-02-03
    • 2014-10-04
    • 2016-06-18
    • 2017-02-20
    • 1970-01-01
    • 2020-09-30
    • 1970-01-01
    • 2021-10-28
    • 2015-02-28
    相关资源
    最近更新 更多