Java AQS的cancelAcquire方法中的 node.next = node; 利于gc?
看Java AQS源码的时候看到cancelAcquire
方法中有这么一句:
node.next = node; // help GC
想问一下为什么这样做就有助于gc。
自己想了很多点感觉都不对。比如如果是为了回收,但是AQS中回收canceled node也不是在这个方法中删除的,其他方法比如acquireQueued
就删除了所有canceled node。
回答:
问题的评论区已经给出答案了。
建议直接去看原文章,写得很好,不过我这里也稍微总结一下。
那篇文章大概意思是,虽然没有这个操作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指针那样引起大量节点无法回收,但这确实是一个存在的问题。
以上是 Java AQS的cancelAcquire方法中的 node.next = node; 利于gc? 的全部内容, 来源链接: utcz.com/p/945172.html