所有!
我在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有什么帮助?
答案 0 :(得分:6)
如果查看jsr166 src,您会发现违规提交
这表明答案在这个错误报告中
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
指向head
和first
指向h.next
:
1 -> 2 -> 3
| |
h first
然后h.next
指向h
,head
指向first
:
1 -> 2 -> 3
| / \
h head first
现在,实际上我们知道只有活动引用指向第一个元素,它本身就是(h.next = h
),我们也知道GC收集没有更多活动引用的对象,所以当方法结束时,GC可以安全地收集列表的(旧)头部,因为h
仅存在于方法的范围内。
话虽如此,有人指出,我同意这一点,即使使用经典的出列方法(即只是让first
指向head.next
而head
指向{ {1}})没有更多的引用指向旧头。但是,在这种情况下,旧的头部悬挂在内存中并且仍然将first
字段指向next
,而在您发布的代码中,唯一剩下的就是指向自身的孤立对象。这可能会触发GC更快地采取行动。
答案 3 :(得分:0)
以下是一个代码示例,用于说明问题:http://pastebin.com/zTsLpUpq。
在runWith()
之后执行GC并为两个版本进行堆转储表示只有一个Item实例。