使用标准 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 库中的错误吗?是否有潜在的解决方法?
最佳答案
在这种情况下不存在“内存泄漏”。内存泄漏定义为应用程序无法回收内存的情况。在这种情况下,没有泄漏,因为所有 XObject
(和 XObject[]
)实例都可以在某个时间点回收。
从 VisualVM 获得的内存分析器快照产生以下观察结果:
- 所有
XObject
(和XObject[]
)实例都是在调用XPathExpression.evaluate
方法时创建的。 XObject
实例在无法从 GC 根访问时被回收。在您的情况下,GC 根是result
和testResult
局部变量,它们在主线程的堆栈中是本地的。
根据以上所述,我假设您的应用程序正在或可能会遇到内存耗尽,而不是内存泄漏。当您有大量来自 XPath 表达式求值的 XObject
/XObject[]
实例时,这是正确的,因为
- 它们仍然可以从 GC root 访问,
- 或者垃圾收集器还没有来回收它们。
第一个问题的唯一解决方案是在需要时将对象保留在内存中。您似乎并没有在代码中违反这一点,但是您的代码肯定可以变得更有效率——您保留了第一个 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 启动标志)。然后你可以决定调整年轻一代的大小,如果你有太多的短命对象被创建,因为有可能将可达的对象移动到老年代,导致可能需要一个主要的集合来回收对象实际上本质上是短暂的。在理想情况下(考虑您发布的代码),应在 for 循环的每几次迭代中执行一次年轻代收集循环,因为循环本地的 XObject
实例应尽快回收因为 block 的局部变量超出范围。
关于java - Xpath内存泄漏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7343744/