看Java AQS源码的时候看到cancelAcquire
方法中有这么一句:
node.next = node; // help GC
想问一下为什么这样做就有助于gc。
自己想了很多点感觉都不对。比如如果是为了回收,但是AQS中回收canceled node也不是在这个方法中删除的,其他方法比如acquireQueued
就删除了所有canceled node。
看Java AQS源码的时候看到cancelAcquire
方法中有这么一句:
node.next = node; // help GC
想问一下为什么这样做就有助于gc。
自己想了很多点感觉都不对。比如如果是为了回收,但是AQS中回收canceled node也不是在这个方法中删除的,其他方法比如acquireQueued
就删除了所有canceled node。
15 回答8.4k 阅读
8 回答6.2k 阅读
4 回答4.4k 阅读✓ 已解决
4 回答4k 阅读
1 回答3k 阅读✓ 已解决
3 回答2.1k 阅读✓ 已解决
2 回答1.5k 阅读✓ 已解决
问题的评论区已经给出答案了。
建议直接去看原文章,写得很好,不过我这里也稍微总结一下。
那篇文章大概意思是,虽然没有这个操作
node.next = node;
我们也能移除这个节点,但是如果这个节点先进入old gen,那么即使它被移除,不可达了,在minor gc时依旧不会清理该节点,且由于该节点存在对young gen的引用,会导致在它之后的节点,如果被移除队列了,依旧无法回收,因为虽然它之后的节点在young gen中,但这个节点被old gen中的一个对象引用着,垃圾回收器并不能回收它,即 跨代引用问题。久而久之会导致堆积大量已经移除队列,但由于在old gen,所以未被垃圾清理的节点。
所以会发生多次full gc,导致程序运行变慢,虽然不会出错。
基于上面讨论,我们在移除一个节点时,应该切断next。切断next有两种方式,让next指向自身,或者让next指向null。这里不采取后者是因为next指向null有特殊含义了————表示队尾。
其实总归来说主要是JVM GC的问题(虽然节点出队已经不可达,但由于其在老年区,所以还是在记忆集中),如果说GC解决了跨代引用的问题,我们也没必要关注这些细节。我搜了一下,在JDK17中,AQS的
cancelAcquire
已经没有这个操作了,说明已经没必要了,大概率是JDK17的GC解决了这个问题。不过其实还有一个小问题,那就是AQS是一个双项队列,按理来说还应该把prev指针指向自己或者为null,不过在其他能移除canceled node的方法中,比如
acquireQueued
,可以看到源码中并没有这样做。所以还是会存在跨代引用问题,一个anceled node会让一个前驱节点因为跨代引用无法回收,虽说不会像next指针那样引起大量节点无法回收,但这确实是一个存在的问题。