Xpath内存泄漏?

时间:2011-09-08 06:03:24

标签: java xpath memory-leaks

使用标准Java库(1.6.0_27)评估XPath表达式时,似乎存在内存泄漏。

请参阅下面的一些代码以重现此问题:

public class XpathTest {

    public static void main(String[] args) throws Exception {
        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        docFactory.setNamespaceAware(true);
        DocumentBuilder builder = docFactory.newDocumentBuilder();
        Document doc = builder.parse("test.xml");

        XPathFactory factory = XPathFactory.newInstance();
        XPath xpath = factory.newXPath();
        XPathExpression expr = xpath.compile("//Product");

        Object result = expr.evaluate(doc, XPathConstants.NODESET);
        NodeList nodes = (NodeList) result;
        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);
            System.out.println(node.getAttributes().getNamedItem("id"));

            XPathExpression testExpr = xpath.compile("Test");
            Object testResult = testExpr.evaluate(node, XPathConstants.NODE);
            Node test = (Node) testResult;
            System.out.println(test.getTextContent());
        }
        System.out.println(nodes.getLength());
    }
}

下面给出了一个示例XML文件:

<Products>
  <Product id='ID0'>
    <Test>0</Test>
  </Product>
  <Product id='ID1'>
    <Test>1</Test>
  </Product>
  <Product id='ID2'>
    <Test>2</Test>
  </Product>
  <Product id='ID3'>
    <Test>3</Test>
  </Product>
  ...
</Products>

当我使用NetBeans概要分析器运行此示例时,即使在垃圾回收之后,com.sun.org.apache.xpath.internal.objects.XObject类的分配也会不断增加。

我是否以错误的方式使用XPath库?这是Java库中的错误吗?是否有潜在的解决方法?

3 个答案:

答案 0 :(得分:2)

不知道这是否会导致内存泄漏,但是:

XPathExpression testExpr = xpath.compile("Test");

不要在for循环中执行此操作。在for循环之外编译一次并重用它。也许XPath对象正在缓存您正在编译以便重用的所有表达式?

答案 1 :(得分:2)

在这种情况下没有“内存泄漏”。内存泄漏定义为应用程序无法回收内存的实例。在这种情况下,没有泄漏,因为所有XObject(和XObject[])实例都可以在某个时间点回收。

从VisualVM获取的内存分析器快照产生以下观察结果:

  • 调用XObject方法时,会创建所有XObject[](和XPathExpression.evaluate)个实例。
  • XObject个实例从GC根目录无法访问时,它们将被回收。在您的情况下,GC根是resulttestResult局部变量,它们是主线程堆栈的本地变量。

基于以上所述,我认为您的应用程序正在经历或可能遇到内存耗尽而不是内存泄漏。当您从XPath表达式评估中获得大量XObject / XObject[]个实例时,这是真的,垃圾收集器尚未回收这些实例,因为

  • 它们仍然可以从GC根目录访问,
  • 或者垃圾收集器还没来回收它们。

第一种方法的唯一解决方案是在需要的时间内将对象保留在内存中。你似乎没有在你的代码中违反它,但你的代码当然可以提高效率 - 你保留第一个XPath表达式的结果,第二个使用它,当然它可以更有效地执行。 //Product/Test可用于检索Test节点,并且还获取父Product节点的id值显示在以下代码段中(仅评估一个XPath表达式而不是两个):

expr = xpath.compile("//Product/Test");
nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
for (int i = 0; i < nodes.getLength(); i++)
{
    Node node = nodes.item(i);
    System.out.println(node.getParentNode().getAttributes().getNamedItem("id"));
    System.out.println(node.getTextContent());
}
System.out.println(nodes.getLength());

就第二个观察而言,您应该获取GC日志(使用verbose:gc JVM启动标志)。然后,如果您创建了太多的短期对象,则可以决定调整年轻代的大小,因为可能的可能对象将被移动到tenured generation,从而可能需要主要集合来回收对象这实际上是短暂的。在一个理想的场景中(考虑到你发布的代码),应该在for循环的每几次迭代中完成一个年轻的gen收集循环,因为循环的本地XObject实例应该在块的后面立即回收局部变量超出范围。

答案 2 :(得分:0)

您说:“在解析文件时,为com.sun.org.apache.xpath.internal.objects.XObject类型分配的对象会不断增加”。

我想你会发现这是设计的。我不知道Apache工具的内部,但您必须期望正常(非流式)DOM和XPath实现使用与源文档大小成比例的内存量。

所以我希望在解析源文档时内存需求会增加。我不希望它会随着对该文档执行更多XPath表达式而增加(在折扣效果之后,某些树构建是懒惰地完成,第一次访问每个节点。)