java.util.concurrent.LinkedBlockingQueue中的奇怪代码

时间:2012-01-11 12:34:46

标签: java garbage-collection puzzle java.util.concurrent

所有!

我在LinkedBlockingQueue中找到了奇怪的代码:

private E dequeue() {
        // assert takeLock.isHeldByCurrentThread();
        Node<E> h = head;
        Node<E> first = h.next;
        h.next = h; // help GC
        head = first;
        E x = first.item;
        first.item = null;
        return x;
}

谁能解释为什么我们需要局部变量h?它对GC有什么帮助?

4 个答案:

答案 0 :(得分:6)

如果查看jsr166 src,您会发现违规提交

http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/LinkedBlockingQueue.java?view=log (见v 1.51)

这表明答案在这个错误报告中

http://bugs.sun.com/view_bug.do?bug_id=6805775

完整的讨论在这个帖子中

http://thread.gmane.org/gmane.comp.java.jsr.166-concurrency/5758

“帮助GC”这个位是为了避免事情流失到终身。

干杯

马特

答案 1 :(得分:4)

也许有点迟了,但目前的解释对我来说完全不能令人满意,我想我有更明智的解释。

首先,每个java GC都会以某种方式从根集中进行某种跟踪。这意味着如果收集旧头,我们将无法读取next变量 - 没有理由这样做。因此,在下一次迭代中收集 IF 头并不重要。

上述句子中的IF是这里的重要部分。设置旁边不同的东西之间的区别对于收集头本身无关紧要,但可能会对其他对象产生影响。

让我们假设一个简单的世代GC:如果头部在年轻的集合中,无论如何它将被收集在下一个GC中。但如果它在旧的集合中,那么只有当我们完成很少发生的完整GC时才会收集它。

那么如果head在旧版本中并且我们做了一个年轻的GC,会发生什么?在这种情况下,JVM假定旧堆中的每个对象仍然存活,并将从旧对象到年轻对象的每个引用添加到年轻GC的根集。这正是赋值在这里避免的:写入旧堆通常受写入障碍或其他东西的保护,以便JVM可以捕获这样的赋值并正确处理它们 - 在我们的例子中它删除了指向的对象next从根本集确实有后果。

简短的例子:

假设我们有1 (old) -> 2 (young) -> 3 (xx)。如果我们现在从列表中删除1和2,我们可能会期望下一个GC将收集这两个元素。但是如果只发生一个年轻的GC并且我们没有删除旧的next指针,则不会收集元素1和2。与此相反,如果我们在1中删除指针,年轻的GC将收集2 ..

答案 2 :(得分:0)

为了更好地理解发生了什么,让我们看看执行代码后列表的样子。首先考虑一个初始列表:

1 -> 2 -> 3

然后h指向headfirst指向h.next

1 -> 2 -> 3
|    |
h    first

然后h.next指向hhead指向first

1 -> 2 -> 3
|   / \
h head first

现在,实际上我们知道只有活动引用指向第一个元素,它本身就是(h.next = h),我们也知道GC收集没有更多活动引用的对象,所以当方法结束时,GC可以安全地收集列表的(旧)头部,因为h仅存在于方法的范围内。

话虽如此,有人指出,我同意这一点,即使使用经典的出列方法(即只是让first指向head.nexthead指向{ {1}})没有更多的引用指向旧头。但是,在这种情况下,旧的头部悬挂在内存中并且仍然将first字段指向next,而在您发布的代码中,唯一剩下的就是指向自身的孤立对象。这可能会触发GC更快地采取行动。

答案 3 :(得分:0)

以下是一个代码示例,用于说明问题:http://pastebin.com/zTsLpUpq。 在runWith()之后执行GC并为两个版本进行堆转储表示只有一个Item实例。