Java 挂在 foreach 循环上
Posted
技术标签:
【中文标题】Java 挂在 foreach 循环上【英文标题】:Java hangs on foreach loop 【发布时间】:2012-05-20 07:27:45 【问题描述】:增加 foreach 循环时,Java 似乎挂起。我找不到任何有类似问题的人,所以也许我只是做错了什么,但我无法想象它是什么。我正在从 Neo4J 数据库中提取一组节点,然后对其进行迭代。我没有在循环期间修改那组节点,但过了一会儿,它挂起。这是执行此操作的代码:
IndexHits<Node> usrs = users.get("Type", "User");
System.out.println("Operating on "+usrs.size()+" Users:");
for (Node u : usrs)
System.out.print(".");
if (inUserBlacklist(u))
continue;
System.out.println("HA");
inUserBlacklist(u) 所做的只是根据预设的一组节点检查节点 u,以查看该节点是否在黑名单中。它不会改变 Node u 的任何内容。
Users 是一个 Neo4J 索引,因此对其调用 get() 应该返回一个可迭代的 IndexHits 对象。这个 foreach 循环运行了 269,938 次 foreach 循环。在该迭代结束时,它会打印“HA”,但它不会再打印另一个“.”。它只是挂在那个点,就在迭代 269,939 之前。这使它成为 foreach 循环的阻塞。总共应该有 270,012 次迭代。
我注意到我的黑名单包含 74 个项目,所有这些项目都应在通过此循环时匹配一次。 270,012 - 74 = 269,938,但这并不能解释为什么它会阻塞。我所能想到的是,当我调用 continue 时,foreach 循环正在增加迭代器上的位置而不增加其计数器。然后它到达集合的末尾,没有更多的东西,但计数器认为它只有 270,012 中的 269,938。
你们中有人知道为什么 foreach 循环会这样吗?
编辑: 堆栈跟踪显示该进程确实卡在 for 循环中(第 116 行):
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.FileDispatcher.pread0(Native Method)
at sun.nio.ch.FileDispatcher.pread(FileDispatcher.java:49)
at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:248)
at sun.nio.ch.IOUtil.read(IOUtil.java:224)
at sun.nio.ch.FileChannelImpl.read(FileChannelImpl.java:663)
at org.neo4j.kernel.impl.nioneo.store.PersistenceRow.readPosition(PersistenceRow.java:91)
at org.neo4j.kernel.impl.nioneo.store.PersistenceWindowPool.acquire(PersistenceWindowPool.java:177)
at org.neo4j.kernel.impl.nioneo.store.CommonAbstractStore.acquireWindow(CommonAbstractStore.java:559)
at org.neo4j.kernel.impl.nioneo.store.RelationshipStore.getChainRecord(RelationshipStore.java:349)
at org.neo4j.kernel.impl.nioneo.xa.ReadTransaction.getMoreRelationships(ReadTransaction.java:121)
at org.neo4j.kernel.impl.nioneo.xa.ReadTransaction.getMoreRelationships(ReadTransaction.java:104)
at org.neo4j.kernel.impl.persistence.PersistenceManager.getMoreRelationships(PersistenceManager.java:108)
at org.neo4j.kernel.impl.core.NodeManager.getMoreRelationships(NodeManager.java:666)
at org.neo4j.kernel.impl.core.NodeImpl.getMoreRelationships(NodeImpl.java:427)
- locked <0x77c9b4a0> (a org.neo4j.kernel.impl.core.NodeImpl)
at org.neo4j.kernel.impl.core.IntArrayIterator.fetchNextOrNull(IntArrayIterator.java:91)
at org.neo4j.kernel.impl.core.IntArrayIterator.fetchNextOrNull(IntArrayIterator.java:36)
at org.neo4j.helpers.collection.PrefetchingIterator.hasNext(PrefetchingIterator.java:55)
at org.neo4j.kernel.impl.traversal.TraversalBranchImpl.next(TraversalBranchImpl.java:128)
at org.neo4j.kernel.PreorderBreadthFirstSelector.next(PreorderBreadthFirstSelector.java:48)
at org.neo4j.kernel.impl.traversal.TraverserImpl$TraverserIterator.fetchNextOrNull(TraverserImpl.java:127)
at org.neo4j.kernel.impl.traversal.TraverserImpl$TraverserIterator.fetchNextOrNull(TraverserImpl.java:94)
at org.neo4j.helpers.collection.PrefetchingIterator.hasNext(PrefetchingIterator.java:55)
at org.neo4j.helpers.collection.IteratorWrapper.hasNext(IteratorWrapper.java:42)
at NodePlacement.LoadFromNode(NodePlacement.java:116)
所以...看起来线程仍在运行并且没有被任何东西阻塞。然而它并没有脱离这部分代码。也许是我的数据库设置导致它陷入了无限循环?
【问题讨论】:
inUserBlacklist
方法有什么作用?如果您在调试器“阻塞”时闯入调试器,堆栈跟踪会是什么样子?
您可能想要启用 GC 日志并查看 Full GC 是否会减慢速度?
只有一个问题。你是如何分析结果的。您将拥有如此长的一系列 .HA...(我假设您编写了一些程序来计算精确的打印)。而对于 74 个案例,您从不打印 HA,会不会是您的计数错误?有时最简单的东西咬得最痛!
我最初包含了一个计数器,只是在 269,000 次迭代后才开始打印 HA。我还包括当前计数器的打印输出。我删除了它并在没有它的情况下运行它,只是为了确保它仍然以最少的代码挂起(并不是我认为我的计数器有问题可能导致它挂起。但话又说回来,我不认为我首先这有什么问题导致它挂起。)。
您能否简要分享一下完整的代码?
【参考方案1】:
当它挂起时,您可以从命令行使用 Ctrl-Break 或在调试器下运行时暂停执行,或者使用 jps 和 jstack 工具获取应用程序的线程转储并查看正在执行的方法。
附带说明,所有 foreach 循环所做的都是从 Iterable
集合或数组中检索 Iterator
实例,并使用迭代器的 hasNext() 和 next() 方法。所以,如果这些方法中的任何一个会挂起,你就会得到你现在看到的。
【讨论】:
在 Unix 系统上,使用 'kill -3 JAVA_PID' 转储所有线程堆栈跟踪。jconsole
还可以帮助您通过 GUI 实现这一目标。【参考方案2】:
我将不顾一切地提出问题出在循环之后的代码中。如所写,代码将为除 inUserBlacklist()
.. 的条目之外的所有条目打印“HA”。如果您的假设基于“HA”输出行的编程计数,那么您给出的计数表明代码完成了循环然后继续做你没有提供的其他事情。
【讨论】:
为了测试这一点,我确保在循环之后发生的唯一事情是我在 Neo4J 事务上调用 finish() 然后关闭 Neo4J 数据库。之后程序终止。finish()
通话能否挂起?您可以在循环后添加一个 println 以验证您的代码不会离开循环吗?
我不确定finish() 是否可以挂起。它是 Neo4J 库的一部分。他们的文档没有提到finish() 或shutdown() 调用是否可以阻塞。但是,我已经验证了代码不会离开 for 循环。
那么剩下的唯一可能就是IndexHits.iterator()
返回的迭代器是问题所在。为了在 for-each 循环中可用,它必须实现 Iterable<Node>
,并且它返回的迭代器被 Java “秘密”使用。【参考方案3】:
嗯,我从来没有弄清楚到底发生了什么,但问题似乎发生在 Neo4J 数据库的磁盘访问中。我回头看看我创建它的方式,我意识到我在创建数据库的方式上犯了一个严重的错误。我重建了整个东西,我现在没有问题。谢谢大家的帮助!
【讨论】:
以上是关于Java 挂在 foreach 循环上的主要内容,如果未能解决你的问题,请参考以下文章