Java 使用 TDB 的 apache Jena 中的 OutOfMemoryError

Java OutOfMemoryError in apache Jena using TDB

您好,我一直在将 Jena 用于一个项目,现在我正在尝试查询图形以存储在普通文件中,以便使用 Hadoop 进行批处理。

我打开一个 TDB Dataset,然后我使用 LIMIT 和 OFFSET 按页查询。

我输出每个文件有 100000 个三元组的文件。

然而,在第 10 个文件中,性能下降,在第 15 个文件中,性能下降了 3 倍,在第 22 个文件中,性能下降到 1%。

我的查询是:

SELECT DISTINCT ?S ?P ?O WHERE {?S ?P ?O .} LIMIT 100000 OFFSET X

查询和写入文件的方法如下一个代码块所示:

public boolean copyGraphPage(int size, int page, String tdbPath, String query, String outputDir, String fileName) throws IllegalArgumentException {
        boolean retVal = true;
        if (size == 0) {
            throw new IllegalArgumentException("The size of the page should be bigger than 0");
        }
        long offset = ((long) size) * page;
        Dataset ds = TDBFactory.createDataset(tdbPath);
        ds.begin(ReadWrite.READ);
        String queryString = (new StringBuilder()).append(query).append(" LIMIT " + size + " OFFSET " + offset).toString();
        QueryExecution qExec = QueryExecutionFactory.create(queryString, ds);
        ResultSet resultSet = qExec.execSelect();
        List<String> resultVars;
        if (resultSet.hasNext()) {
            resultVars = resultSet.getResultVars();
            String fullyQualifiedPath = joinPath(outputDir, fileName, "txt");
            try (BufferedWriter bwr = new BufferedWriter(new OutputStreamWriter(new BufferedOutputStream(
                    new FileOutputStream(fullyQualifiedPath)), "UTF-8"))) {
                while (resultSet.hasNext()) {
                    QuerySolution next = resultSet.next();
                    StringBuffer sb = new StringBuffer();
                    sb.append(next.get(resultVars.get(0)).toString()).append(" ").
                            append(next.get(resultVars.get(1)).toString()).append(" ").
                            append(next.get(resultVars.get(2)).toString());
                    bwr.write(sb.toString());
                    bwr.newLine();
                }
                qExec.close();
                ds.end();
                ds.close();
                bwr.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
            resultVars = null;
            qExec = null;
            resultSet = null;
            ds = null;
        } else {
            retVal = false;
        }
        return retVal;
    }

空变量在那里是因为我不知道那里是否有可能泄漏。

但是在第 22 个文件之后程序失败并显示以下消息:

java.lang.OutOfMemoryError: GC overhead limit exceeded

    at org.apache.jena.ext.com.google.common.cache.LocalCache$EntryFactory.newEntry(LocalCache.java:455)
    at org.apache.jena.ext.com.google.common.cache.LocalCache$Segment.newEntry(LocalCache.java:2144)
    at org.apache.jena.ext.com.google.common.cache.LocalCache$Segment.put(LocalCache.java:3010)
    at org.apache.jena.ext.com.google.common.cache.LocalCache.put(LocalCache.java:4365)
    at org.apache.jena.ext.com.google.common.cache.LocalCache$LocalManualCache.put(LocalCache.java:5077)
    at org.apache.jena.atlas.lib.cache.CacheGuava.put(CacheGuava.java:76)
    at org.apache.jena.tdb.store.nodetable.NodeTableCache.cacheUpdate(NodeTableCache.java:205)
    at org.apache.jena.tdb.store.nodetable.NodeTableCache._retrieveNodeByNodeId(NodeTableCache.java:129)
    at org.apache.jena.tdb.store.nodetable.NodeTableCache.getNodeForNodeId(NodeTableCache.java:82)
    at org.apache.jena.tdb.store.nodetable.NodeTableWrapper.getNodeForNodeId(NodeTableWrapper.java:50)
    at org.apache.jena.tdb.store.nodetable.NodeTableInline.getNodeForNodeId(NodeTableInline.java:67)
    at org.apache.jena.tdb.store.nodetable.NodeTableWrapper.getNodeForNodeId(NodeTableWrapper.java:50)
    at org.apache.jena.tdb.solver.BindingTDB.get1(BindingTDB.java:122)
    at org.apache.jena.sparql.engine.binding.BindingBase.get(BindingBase.java:121)
    at org.apache.jena.sparql.engine.binding.BindingProjectBase.get1(BindingProjectBase.java:52)
    at org.apache.jena.sparql.engine.binding.BindingBase.get(BindingBase.java:121)
    at org.apache.jena.sparql.engine.binding.BindingProjectBase.get1(BindingProjectBase.java:52)
    at org.apache.jena.sparql.engine.binding.BindingBase.get(BindingBase.java:121)
    at org.apache.jena.sparql.engine.binding.BindingBase.hashCode(BindingBase.java:201)
    at org.apache.jena.sparql.engine.binding.BindingBase.hashCode(BindingBase.java:183)
    at java.util.HashMap.hash(HashMap.java:338)
    at java.util.HashMap.containsKey(HashMap.java:595)
    at java.util.HashSet.contains(HashSet.java:203)
    at org.apache.jena.sparql.engine.iterator.QueryIterDistinct.getInputNextUnseen(QueryIterDistinct.java:106)
    at org.apache.jena.sparql.engine.iterator.QueryIterDistinct.hasNextBinding(QueryIterDistinct.java:70)
    at org.apache.jena.sparql.engine.iterator.QueryIteratorBase.hasNext(QueryIteratorBase.java:114)
    at org.apache.jena.sparql.engine.iterator.QueryIterSlice.hasNextBinding(QueryIterSlice.java:76)
    at org.apache.jena.sparql.engine.iterator.QueryIteratorBase.hasNext(QueryIteratorBase.java:114)
    at org.apache.jena.sparql.engine.iterator.QueryIteratorWrapper.hasNextBinding(QueryIteratorWrapper.java:39)
    at org.apache.jena.sparql.engine.iterator.QueryIteratorBase.hasNext(QueryIteratorBase.java:114)
    at org.apache.jena.sparql.engine.iterator.QueryIteratorWrapper.hasNextBinding(QueryIteratorWrapper.java:39)
    at org.apache.jena.sparql.engine.iterator.QueryIteratorBase.hasNext(QueryIteratorBase.java:114)

Disconnected from the target VM, address: '127.0.0.1:57723', transport: 'socket'

Process finished with exit code 255

内存查看器显示查询页面后内存使用量增加:

很明显,Jena LocalCache 正在填满,我已将 Xmx 更改为 2048m,将 Xms 更改为 512m,结果相同。没有任何变化。

我需要更多内存吗?

我需要清除一些东西吗?

我需要停止程序并分段执行吗?

我的查询有误吗?

OFFSET与它有什么关系吗?

我在一些旧邮件帖子中读到您可以关闭缓存,但我找不到任何方法来做到这一点。有没有办法关闭缓存?

我知道这是一个非常困难的问题,但我感谢您的帮助。

你似乎在某处有内存泄漏,这只是一个猜测,但试试这个:

TDBFactory.release(ds);

REF:https://jena.apache.org/documentation/javadoc/tdb/org/apache/jena/tdb/TDBFactory.html#release-org.apache.jena.query.Dataset-

It is clear that Jena LocalCache is filling up

这是 TDB 节点缓存 - 每个数据集本身通常需要 1.5G(2G 更好)。此缓存在 JVM 的生命周期内持续存在。

一个java 2G的堆以今天的标准来看是一个小的Java堆。如果你必须使用小堆,你可以在 32 位模式下尝试 运行(在 TDB 中称为 "Direct mode"),但这性能较差(主要是因为节点缓存较小并且在这个数据集中你有足够的节点导致小型缓存的缓存流失)。

节点缓存是堆耗尽的主要原因,但查询在其他地方消耗内存,每个查询,DISTINCT

DISTINCT不一定便宜。它需要记住它看到的所有内容,才能知道新行是第一次出现还是已经看到。

Apache Jena 确实优化了(TopN 查询)的某些情况,但截止 优化默认为 1000。请参阅代码中的 OpTopN

否则它将收集到目前为止看到的所有行。您越深入数据集,节点缓存中的内容就越多,也比 DISTINCT 过滤器中的内容更多。

Do I need more memory?

是的,更多堆。合理的最小值是每个 TDB 数据集 2G,然后 Java 本身需要什么(比如 0.5G)加上你的程序和查询工作区。