recycler文件夹可以删除吗 recycler( 二 )

= handle;//这里将stack置为null,是为了方便stack被回收 。//如果Stack不再使用,期望被GC回收,发现handle中还持有stack的引用,那么就无法被GC回收,从而造成内存泄漏//在从对象池中再次取出该对象时,stack还会被重新赋予handle.stack = null;//注意这里用lazySet来延迟更新writeIndex 。只有当writeIndex更新之后,在创建线程中才可以看到该待回收对象//保证线程最终可见而不保证立即可见的原因就是 其实这里Netty还是为了性能考虑避免执行内存屏障指令的开销 。//况且这里也并不需要考虑线程的可见性,当创建线程调用scavengeSome从weakOrderQueue链表中回收对象时,看不到当前节点weakOrderQueue//新添加的对象也没关系,因为是多线程一起回收,所以继续找下一个节点就好 。及时全没看到,大不了就在创建一个对象 。主要还是为了提高weakOrderQueue的写入性能tail.lazySet(writeIndex + 1);}首先第一步就要设置回收对象DefaultHandler中的lastRecycledId ,将其设置为该回收线程Id,表示该回收对象最近一次是由当前回收线程回收的 。此时的DefaultHandler中 recycleId != lastRecycledId ,对象处于半回收状态 。控制回收线程的回收频率(只回收 1 / 8 的对象),大家是否还记得我们在《9.5 转移回收对象》小节中介绍 stack#scavengeSome方法 的时候,在创建线程从Stack中的WeakOrderQueue链表中转移对象到数组栈中的时候,也会被回收频率进行控制,只转移 1 / 8 的对象 。所以这里我们可以看到回收频率的控制在多线程回收对象的时候会控制两次,netty认为越早做回收频率控制越好这样可以避免weakOrderQueue中的容量迅速的增长从而失去控制 。在WeakOrderQueue结构中,当我们向Link链表添加回收对象时,都会向Link链表的尾结点中添加回收对象,如果当前尾结点容量已经满了 writeIndex = tail.get()) == LINK_CAPACITY ,我们就需要新创建一个Link节点,并将tail指针指向新的Link节点更新尾结点 。最后将回收对象回收至新的尾结点中 。当然我们要考虑到 availableSharedCapacity 容量的限制,如果容量不够了,就不能在新建Link节点,直接将回收对象丢弃,停止回收 。private static final class Head {Link newLink() {//此处的availableSharedCapacity可能已经被多个回收线程改变,因为availableSharedCapacity是用来控制回收线程回收的总容量限制//每个回收线程再回收对象时都需要更新availableSharedCapacityreturn reserveSpaceForLink(availableSharedCapacity) ? new Link() : null;}//此处目的是为接下来要创建的link预留空间容量static boolean reserveSpaceForLink(AtomicInteger availableSharedCapacity) {for (;;) {//获取stack中允许异线程回收对象的总容量(异线程还能为该stack收集多少对象)int available = availableSharedCapacity.get();//当availbale可供回收容量小于一个Link时,说明异线程回收对象已经达到上限,不能在为stack回收对象了if (available < LINK_CAPACITY) {return false;}//为Link预留到一个Link的空间容量,更新availableSharedCapacityif (availableSharedCapacity.compareAndSet(available, available - LINK_CAPACITY)) {return true;}}}}到这里Recycler对象池的整个多线程无锁化回收对象的流程笔者就为大家介绍完了 。
但是这里还有两个点,笔者想要和大家再强调一下:
第一:为什么这里会将handle.stack设置为null?
不知大家还记不记得我们在介绍 stack#scavengeSome方法 的时候专门提到,在创建线程遍历WeakOrderQueue链表将链表中的待回收对象转移至stack中的数组栈时,会将待回收对象的DefaultHandler持有的stack重新设置为其创建线程对应的stack 。
boolean transfer(Stack<?> dst) {.................省略..............//重新为defaultHandler设置其所属stack(初始创建该handler的线程对应的stack)//该defaultHandler在被回收对象回收的时候,会将其stack置为null,防止极端情况下,创建线程挂掉,对应stack无法被GCelement.stack = dst;.................省略..............}而这里在回收线程向WeakOrderQueue节点添加回收对象时先将 handle.stack设置为 null,而在转移回收对象时又将 handle.stack 设置回来,这不是多此一举吗?
其实并不是多此一举,这样设计是非常有必要的,我们假设一种极端的情况,当创建线程挂掉并被GC回收之后,其实stack中存储的回收对象已经不可能在被使用到了,stack应该也被回收掉 。但是如果这里回收线程在回收的时候不将对象持有的stack设置为null的话,直接添加到了WeakOrderQueue节点中,当创建被GC掉的时候,由于这条引用链的存在导致对应stack永远不会被GC掉,造成内存泄露 。
所以笔者在本文中多次强调,当我们在设计比较复杂的程序结构时,对于对象之间的引用关系,一定要时刻保持清晰的认识,防止内存泄露 。