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

java.util.concurrent.LinkedBlockingQueue中的奇怪代码,java,garbage-collection,puzzle,java.util.concurrent,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 = firs

全部

我在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;
}
private出列(){
//断言takeLock.isHeldByCurrentThread();
节点h=头部;
节点优先=h.next;
h、 next=h;//帮助GC
头=第一;
E x=第一项;
first.item=null;
返回x;
}

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

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

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收集没有更多活动引用的对象,因此当方法结束时,(旧的)由于
h
仅存在于方法的范围内,因此GC可以安全地收集列表的标题


话虽如此,有人指出,我同意这一点,即即使使用经典的出列方法(即只需将
第一个
指向
头部。下一个
头部
指向
第一个
),也没有更多指向旧头部的引用。然而,在这个场景中,旧的头部在内存中悬空,并且仍然有其
next
字段指向
first
,而在您发布的代码中,只剩下一个指向自身的孤立对象。这可能会触发GC更快地执行操作。

下面是一个代码示例,说明了这个问题:。
runWith()
之后执行GC并对这两个版本进行堆转储表示只有一个项实例。

可能有点晚,但当前的解释对我来说完全不满意,我认为我得到了一个更合理的解释

首先,每个JavaGC都以某种方式从根集进行某种跟踪。这意味着,如果收集了旧磁头,我们无论如何都不会读取
next
变量-没有理由这样做。因此,如果在下一次迭代中收集到头部,则无所谓

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

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

那么,如果head在旧的集合中,我们做一个年轻的GC,会发生什么呢?在这种情况下,JVM假设旧堆中的每个对象仍然是活动的,并将从旧对象到新对象的每个引用添加到年轻GC的根集中。这正是分配所避免的:写入旧堆通常会受到写屏障或其他保护,以便JVM能够捕获此类分配并正确处理它们——在我们的例子中,它会从根集中删除指向的对象
next
,这确实会产生后果

简短示例:

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

如果您查看,那么您将发现有问题的提交,向下滚动到v1.51

这说明答案就在这里

完整的讨论可以在


“帮助GC”一词是关于避免事情流血到终身监禁中。

向下投票,因为即使没有
h,h也不会有更多的参考文献。next=h
@Vadzim:我不是在说,我只是解释他发布的代码中发生了什么。问题不是这个。这是关于为什么不仅仅是
first=head=head.next
。不过还是谢谢你的图片。注意,head总是不包含第一项本身。为什么对可访问对象的引用会影响GC?@Vadzim:理论上不会,但在我看到GC的实际实现之前,我想我可以假设任何细微的差异都可能影响它决定收集和对象的速度。可能注释已经过时(你能找到版本控制并跟踪提交的注释吗?):有趣的是,这个临时变量和注释只在java 7中添加。所以这肯定是出于某种目的。这就是openjdk,Mac上Oracle的jdk 6(1.6.0_29-b11-402.jdk)中的LinkedBlockingQueue有temp变量。至少java 5中肯定没有这个变量。那么GC的“帮助”是什么呢?也许在某些情况下,我不知道,也许我们可以找到写评论的人:)我真的看不出你说的和我说的有什么区别。我还说过,不移除下一个可能会导致GC更快地收集头部。@Tudor我解释了哪种机制会导致行为的改变,在哪种情况下会发生,这对我来说肯定比“某些事情可能导致GC采取不同的行动”更为重要。另外,这不是关于早期收集head(这显然是胡说八道),而是关于后续元素——不同的对象。我在评论中指出,在没有实际实现的情况下,我只能推测该任务应该做什么。另一方面,您在没有提供任何源代码和使用诸如“young GC”之类的术语的情况下做了很多绝对的陈述