2011-09-08 83 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表達式針對該文檔執行而增加(在對某些樹形結構進行延遲處理後,首次對每個節點進行訪問時進行了折扣後)。