11

全部!

我在 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

4 回答 4

7

如果你查看jsr166 src,你会发现有问题的提交,向下滚动到 v1.51

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

完整的讨论可以在jsr.166 邮件列表线程中找到

“帮助 GC”位是关于避免事情渗入终身。

于 2012-01-15T10:21:42.493 回答
5

也许有点晚了,但目前的解释对我来说完全不满意,我想我有一个更明智的解释。

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

上面句子中的 IF 是这里的重要部分。在不同的东西旁边设置之间的差异对于收集头部本身并不重要,但可能会对其他对象产生影响。

让我们假设一个简单的分代 GC:如果 head 在年轻集合中,无论如何它将在下一次 GC 中被收集。但是如果它在旧集合中,它只会在我们执行很少发生的完整 GC 时被收集。

那么如果 head 在旧集合中并且我们进行年轻 GC 会发生什么?在这种情况下,JVM 假定旧堆中的每个对象都还活着,并将从旧对象到年轻对象的每个引用添加到年轻 GC 的根集中。这正是分配在这里避免的:写入旧堆通常受到写屏障或其他东西的保护,以便 JVM 可以捕获此类分配并正确处理它们 - 在我们的例子中,它next从根集中删除指向的对象确实有后果。

简短的例子:

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

于 2012-01-14T03:44:13.413 回答
0

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

1 -> 2 -> 3

然后h指向head和:first_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指向first),也不再有指向旧头的引用。但是,在这种情况下,旧的头在内存中悬空,仍然有它的next字段指向first,而在您发布的代码中,唯一剩下的是一个指向自身的孤立对象。这可能会触发 GC 更快地采取行动。

于 2012-01-11T12:51:14.567 回答
0

这是一个说明问题的代码示例:http: //pastebin.com/zTsLpUpq。在两个版本之后执行 GCrunWith()并进行堆转储表明只有一个 Item 实例。

于 2012-01-11T15:37:05.820 回答