Java xpath内存泄漏?

时间:2015-09-17 15:50:53

标签: java xpath memory-leaks javafx

我的情况一直困扰着我几个月:我不断获得OOM异常(堆空间)和检查堆转储我发现了数百万个我从未分配但可能在底层库中分配的对象实例。经过大量的血,汗和泪,我设法本地化了产生内存泄漏的代码,我编写了一个最小的,完整的,可验证的代码示例来说明这一点:

import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.scene.web.WebEngine;
import javafx.stage.Stage;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class MVC extends Application implements ChangeListener<Worker.State>{

    private final WebEngine engine = new WebEngine();
    private final String url = "https://biblio.ugent.be/publication?sort=publicationstatus.desc&sort=year.desc&limit=250&start=197000";
    private final XPath x = XPathFactory.newInstance().newXPath();

    @Override
    public void start(Stage primaryStage) throws Exception {
        System.setProperty("jsse.enableSNIExtension", "false");
        engine.getLoadWorker().stateProperty().addListener(this);
        engine.load(url);
    }

    public static void main(String[] args) {
        launch(args);
    }

    private NodeList eval(Node context, String xpath) throws XPathExpressionException{
        return (NodeList)x.evaluate(xpath, context, XPathConstants.NODESET);
    }

    @Override
    public void changed(ObservableValue<? extends Worker.State> observable, Worker.State oldValue, Worker.State newValue) {
        if (newValue==Worker.State.SUCCEEDED) {
            try {
                while(true){
                    NodeList eval = eval(engine.getDocument(), "//span[@class='title']");
                    int s = eval.getLength();
                }
            } catch (XPathExpressionException ex) {
                Logger.getLogger(MVC.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

代码执行以下操作:

  • 使用JavaFX WebEngine
  • 加载文档
  • 无休止地使用javax.xml软件包对文档执行xpath查询,而不存储结果或指针

要运行,请创建JavaFX应用程序,在默认包中添加名为MVC.java的文件,输入代码并点击运行。任何分析工具(我使用VisualVM)都应该快速向您显示,在几分钟内,堆就会无法控制地增长。似乎已分配以下对象但从未发布:

  • java.util.HashMap$Node
  • com.sun.webkit.Disposer$WeakDisposerRecord
  • com.sun.webkit.dom.NamedNodeMapImpl$SelfDisposer
  • java.util.concurrent.LinkedBlockingQueue$Node

每次运行代码时都会发生此行为,无论我加载的url还是我在文档上执行的xpath。

我测试的设置:

  • MBP运行OS X Yosemite(最新)
  • JDK 1.8.0_60

有人可以重现这个问题吗?这是实际的内存泄漏吗?我有什么可以做的吗?

修改

我的一位同事在使用JDK 1.8.0_45的w7机器上重现了这个问题,它也发生在Ubuntu服务器上。

编辑2

我已经测试了jaxen作为javax.xml包的替代方法,但结果是一样的,这让我相信这个bug位于sun webkit的深处

1 个答案:

答案 0 :(得分:7)

我也在Ubuntu中用jdk1.8.60复制了泄漏。我做了一些分析和调试,核心原因很简单,可以轻松修复。 XPath中没有内存泄漏。

有一个类 com.sun.webkit.Disposer ,它正在对在XPath评估期间创建的一些内部结构进行连续清理。处理程序内部通过 Invoker.getInvoker()。invokeOnEventThread(this); 调用清理。如果您反编译代码,就可以看到它。调用者有不同的实现,使用不同的线程。如果您在JavaFX中工作,Invoker会在JavaFX线程中定期执行清理。

但是,您的更改侦听器方法也在JavaFX线程中调用,并且它永远不会返回,因此清理从未发生过。

我修改了你的代码,因此 changed 方法只生成一个新线程并返回,并且处理是异步完成的。猜猜是什么 - 记忆不再增长:

@Override
public void changed(ObservableValue<? extends Worker.State> observable, Worker.State oldValue, Worker.State newValue) {
    if (newValue==Worker.State.SUCCEEDED) {
        new Thread(() ->{
            try {
                while(true){
                    NodeList eval = eval(engine.getDocument(), "//span[@class='title']");
                    int s = eval.getLength();
                }
            } catch (XPathExpressionException ex) {
                Logger.getLogger(MVC.class.getName()).log(Level.SEVERE, null, ex);
            }
        }).start();
    }
}