使用标准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库中的错误吗?是否有潜在的解决方法?
答案 0 :(得分:2)
不知道这是否会导致内存泄漏,但是:
XPathExpression testExpr = xpath.compile("Test");
不要在for循环中执行此操作。在for循环之外编译一次并重用它。也许XPath对象正在缓存您正在编译以便重用的所有表达式?
答案 1 :(得分:2)
在这种情况下没有“内存泄漏”。内存泄漏定义为应用程序无法回收内存的实例。在这种情况下,没有泄漏,因为所有XObject
(和XObject[]
)实例都可以在某个时间点回收。
从VisualVM获取的内存分析器快照产生以下观察结果:
XObject
方法时,会创建所有XObject[]
(和XPathExpression.evaluate
)个实例。XObject
个实例从GC根目录无法访问时,它们将被回收。在您的情况下,GC根是result
和testResult
局部变量,它们是主线程堆栈的本地变量。基于以上所述,我认为您的应用程序正在经历或可能遇到内存耗尽而不是内存泄漏。当您从XPath表达式评估中获得大量XObject
/ XObject[]
个实例时,这是真的,垃圾收集器尚未回收这些实例,因为
第一种方法的唯一解决方案是在需要的时间内将对象保留在内存中。你似乎没有在你的代码中违反它,但你的代码当然可以提高效率 - 你保留第一个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表达式而增加(在折扣效果之后,某些树构建是懒惰地完成,第一次访问每个节点。)