2011-09-08 88 views
1

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

请参阅以下一些代码来reproduct这个问题:

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 Profiler这个例子com.sun.org.apache.xpath.internal.objects.XObject类不断增加,即使在垃圾回收之后。

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

+0

嗯,这将会非常有趣。你是如何测试你的假设的?用探查器?您的示例XML文件有多长时间?很可能有一个内部高速缓存来加速对“评估”的后续调用... –

+0

示例XML文件具有100,000条记录。我正在使用NetBeans分析器,并且为文件com.sun.org.apache.xpath.internal.objects.XObject分配的对象在分析文件时不断增加。 – Bob

+0

这是很多记录。对于性能(不仅是内存)的原因,你应该避免使用XPath,并尽量使用DOM API(另请参阅[我的基准测试](http://stackoverflow.com/questions/6340802/java-xpath-apache-jaxp-实施绩效))。 –

回答

2

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

甲内存分析器快照产生以下的观察:

  • 被调用XPathExpression.evaluate方法时所创建的所有XObject(和XObject[])实例。
  • 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启动标志)。如果您创建了太多短命物体,您可以决定调整年轻一代的尺寸,因为可能有可能将可到达的物体移动到终身代中,从而导致可能需要收集主要的物品实际上它们本质上是短暂的。在一个理想的情况下(考虑你的发布代码),一个年轻的gen收集周期应该每for循环迭代一次,因为环路本地的XObject实例只要块的局部变量消失就应该被回收的范围。

+0

包含的程序只是一个测试程序,用于重现我在应用程序中发现的问题。在应用程序中,我实际上需要处理存储在数据库中的片段,并使用XPath表达式从这些片段中提取属性。可能有数百万条产品记录,这将需要数百万次的Xpath表达式评估。 我可以看看GC的建议,但如果我让应用程序运行足够长时间,我会认为GC将能够回收内存。 – Bob

+0

@Bob,至少有两种GC循环。如果你的短寿命物体可以超越几代年轻一代的GC循环,那么一旦年轻一代填满后,它们将被提升到年老一代。在那个时候,你需要一个主要的集合而不是一个小集合来回收这些对象。这就是为什么你需要调整年轻一代的规模以扩大规模(我相信默认值是4M),所以年轻的发展周期(在这种情况下会更频繁地发生)会发现大多数对象是无法从GC根目录获得。 –

2

不知道这可能会导致内存泄漏,但:

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

不要for循环做到这一点的。在for循环之外编译一次并重用它。也许XPath对象正在缓存所有正在编译的表达式以供重用?

+2

这当然是真的,尽管我发现'compile'只会弥补CPU和内存消耗非常少,与XPathFactory.newInstance()和expr相比。评估()'(见这些[基准在这里](http://stackoverflow.com/questions/6340802/java-xpath-apache-jaxp-implementation-performance)) –

+0

我已经尝试过,但没有运气。问题似乎与评估方法一致。如果我评估评估声明,那么就没有泄漏。 – Bob

0

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

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

所以我希望内存需求随着源文档的解析而增加。我不希望它会随着更多的XPath表达式针对该文档执行而增加(在对某些树形结构进行延迟处理后,首次对每个节点进行访问时进行了折扣后)。