netty之对象池个人理解

作用

解决频繁GC问题

关键点

多线程从池中获取对象,多线程回收对象

思考

假如获取对象和回收对象的方法上锁,那么可以起到池的作用,但显然不是个好方法,效率太低,得不偿失。

如何无锁化?

每个线程对应一个对象池,并且只有该线程可以获取对象和回收对象。那么就可以实现池的无锁化。
在netty中,采用stack来实现,pop获取对象,push回收对象。
绑定的方法,显然是采用ThreadLocal方式,在netty中自行实现了一个FastThreadLocal。
但是,实际问题是,线程A分配了对象,经过一系列传递,在线程B中调用回收对象,如何解决?

如何在其他线程中无锁化回收?

只要不直接操作对象池,而是将回收对象另外存放,类似放到垃圾箱中,先实现对象交还,等A线程有空了,处理掉垃圾箱中的东西,处理的方式,即是挪移到自身的对象池中,以实现复用。
适合实现无锁化垃圾箱的数据结构即是队列,因为队列的存取不会冲突。在netty中,采用了Recycler.WeakOrderQueue.Link对象,保存着需要回收的对象

何时将垃圾箱中的对象挪到自己的池中来?

当自身的对象池用光的时候,才将垃圾箱中的对象挪移过来

   Recycler.DefaultHandle<T> pop() {
            int size = this.size;
            if (size == 0) {
                if (!this.scavenge()) {
                    return null;
                }

                size = this.size;
            }
            ……
   }

假设挪移的对象非常多,一次性挪移会耗资源,那么如何摊还?

摊还对象

在Link之上,netty中采用了WeakOrderQueue来实现摊还。进一步包装了Link。
每个线程都会构建一个WeakOrderQueue,采用和Thread绑定的Stack对象做索引。
WeakOrderQueue中有一个弱引用对象对Thread进行引用。在引用失效时,会做一次全量的挪移。
从最初的线程对象,可以拿到与其绑定的stack对象,接着可以拿到WeakOrderQueue对象,然后WeakOrderQueue对象就可以拿到Link。将该Link中所有的对象挪移回池中即可。每次挪移只操作1个WeakOrderQueue,然后按照环形的思路,头指针挪移到下一个。

这样产生了新的问题,现在已经没有对象可用的情况下,才会尝试挪移,但也只是操作1个Queue,假设腾不出对象怎么办?如果一直操作直到找到对象为止,那么由于上面将队列设计成环,在队列中无对象可用时,就会成为死循环。

非固定大小的池

池是一个动态维护对象的地方,上面提到的的问题,当queue中腾不出对象时,简单分配一个新对象,然后继续头指针挪到下一队列元素即可。

那么,什么时候回收?

回收方式

通过某种自增计数handleRecycleCount,达到某个设定阈值时,将回收掉。

boolean dropHandle(Recycler.DefaultHandle<?> handle) {
    if (!handle.hasBeenRecycled) {
        if ((++this.handleRecycleCount & this.ratioMask) != 0) {
            return true;
        }
        handle.hasBeenRecycled = true;
    }

    return false;
}

为何要采用这种方式来回收?回收数又是多少

计数回收

这个问题可能是,new一个小对象时,是在新生代中的,当一个新生代对象熬过15次回收时,会升为老年代对象,而回收老年代比回收新生代效率低10倍。
在netty中ratioMask的值默认设置为8。也就是说,池内的对象只能用8次,就回收掉。
具体的深意还不明。

总结

至此,通过一个个的小问题,结合netty中的源码实现,对逐个问题进行分析解决,最终展开了netty中实现对象池维护的方式。

发布了25 篇原创文章 · 获赞 1 · 访问量 3875

猜你喜欢

转载自blog.csdn.net/a215095167/article/details/105115661