netty对象池详解

netty引用计数机制介绍中,我们说到了对象池,这里对对象池做一个详细的介绍。
这里引用上节的内容,介绍下对象池的作用。

对象池其实就是缓存一些对象从而避免大量创建同一个类型的对象,类似线程池的概念。对象池缓存了一些已经创建好的对象,避免需要时才创建对象,同时限制了实例的个数。池化技术最终要的就是重复的使用池内已经创建的对象。从上面的内容就可以看出对象池适用于以下几个场景:
1.创建对象的开销大
2.会创建大量的实例
3.限制一些资源的使用
DirectByteBuffer的缺点在于分配和回收的的代价相对较大,因此DirectByteBuffer适用于缓冲区可以重复使用的场景。

在引用计数变为0的时候,会进行对象的回收,对于池化对象,我们需要将该对象放回到池中,否则会有内存泄露的问题。

内存泄漏:在netty中的内存泄漏是指GC自动回收了refCnt>0的对象的内存,这种情况下使得该对象无法被重建,同时也无法被归还回到netty对象池中,所以即使该内存空置可用,但是应用层仍然标志了该内存被使用中,从而使得该块内存在应用的整个生命周期中无法被使用,造成了浪费。

这里我们来看下netty对象池的实现是怎样的
这里拿PooledByteBuf这个类来作为入口,该类是池化bytebuf所要继承的抽象类,上次我们说到了它的deallocate()方法,用于在引用计数为0后释放资源
在这里插入图片描述
如图,可以看到其中两个变量,leak和recyclerHandle,其中leak用于内存泄露的监测,recyclerHandle负责对象在使用完毕后放回对象池中,后者使我们要详细去介绍的。我们看下recyclerHandle的外部类实现
在这里插入图片描述
可以看到doc介绍说是一个基于Threadlocal实现的轻量级对象池
我们来看一下它内部的成员变量有哪些,并做一些简单的介绍先:

	/*
	 *唯一ID生成器
     * 用在两处:
     * 1、当前线程ID
     * 2、WeakOrderQueue的id
    */
	private static final AtomicInteger ID_GENERATOR = new AtomicInteger(Integer.MIN_VALUE);
    private static final int OWN_THREAD_ID = ID_GENERATOR.getAndIncrement();
    //Bytebuf最大容量及初始化容量,静态模块中是这两个变量的计算
    private static final int DEFAULT_MAX_CAPACITY;
    private static final int INITIAL_CAPACITY;
    static {
        // In the future, we might have different maxCapacity for different object types.
        // e.g. io.netty.recycler.maxCapacity.writeTask
        //      io.netty.recycler.maxCapacity.outboundBuffer
        int maxCapacity = SystemPropertyUtil.getInt("io.netty.recycler.maxCapacity.default", 0);
        if (maxCapacity <= 0) {
            // TODO: Some arbitrary large number - should adjust as we get more production experience.
            maxCapacity = 262144;
        }

        DEFAULT_MAX_CAPACITY = maxCapacity;
        if (logger.isDebugEnabled()) {
            logger.debug("-Dio.netty.recycler.maxCapacity.default: {}", DEFAULT_MAX_CAPACITY);
        }

        INITIAL_CAPACITY = Math.min(DEFAULT_MAX_CAPACITY, 256);
    }
    //存储Stack的最大容量
    private final int maxCapacity;
    //借助threadLocalmap保存栈
    private final FastThreadLocal<Stack<T>> threadLocal = new FastThreadLocal<Stack<T>>() {
        @Override
        protected Stack<T> initialValue() {
            return new Stack<T>(Recycler.this, Thread.currentThread(), maxCapacity);
        }
    };
     /**
     * 创建一个对象
     * 1、由子类进行复写,所以使用protected修饰
     * 2、传入Handle对象,对创建出来的对象进行回收操作
     */
    protected abstract T newObject(Handle handle);
    //处理回收的接口,实现也在该类
    public interface Handle { }
    //借助threadLocalmap保存由WeakOrderQueue组成的map
    private static final FastThreadLocal<Map<Stack<?>, WeakOrderQueue>> DELAYED_RECYCLED =
            new FastThreadLocal<Map<Stack<?>, WeakOrderQueue>>() {
        @Override
        protected Map<Stack<?>, WeakOrderQueue> initialValue() {
            return new WeakHashMap<Stack<?>, WeakOrderQueue>();
        }
    };

继续来看下其初始化方法

	protected Recycler() {
        this(DEFAULT_MAX_CAPACITY);
    }

    protected Recycler(int maxCapacity) {
        this.maxCapacity = Math.max(0, maxCapacity);
    }
    //调用方式例如:
	private static final Recycler<PooledHeapByteBuf> RECYCLER = new Recycler<PooledHeapByteBuf>() {
        @Override
        protected PooledHeapByteBuf newObject(Handle handle) {
            return new PooledHeapByteBuf(handle, 0);
        }
    };
对象的获取

下面我们来看下对象是怎么从对象池中得到的

public final T get() {
        Stack<T> stack = threadLocal.get();
        DefaultHandle handle = stack.pop();
        if (handle == null) {
        	//为空就创建DefaultHandle
            handle = stack.newHandle();
            //并创建对象赋值给DefaultHandle
            handle.value = newObject(handle);
        }
        return (T) handle.value;
    }

重点看下stack.pop()方法

//Stack
@SuppressWarnings({ "unchecked", "rawtypes" })
DefaultHandle<T> pop() {
    int size = this.size;
    //如果Stack当前数组大小为0,表示没有现成可用的对象,
    //尝试使用scavenge进行回收,大致过程就是从链接的一个个WeakOrderQueue队列中取出对象使用transfer方法将对象放入到Stack容器数组中
    if (size == 0) {
    	//简要概括scavenge流程就是Stack从“背后”的Queue中获取可用的实例,
    	//如果Queue中没有可用实例就遍历到下一个Queue(Queue组成了一个链表)
        if (!scavenge()) {
            //没有可回收对象,则返回null
            return null;
        }
        size = this.size;
    }
    //如果上面成功回收了对象,则这里返回刚回收的对象
    //**并将该对象所在的数组位置置空,表示对象被分配出去**
    //**这里会预留空间给被拿出去的对象,如果不将对象在回收时放回,那么这一块预留空间将永远不会被利用,从而造成内存泄露和浪费**
    size --;
    DefaultHandle ret = elements[size];
    elements[size] = null;
    if (ret.lastRecycledId != ret.recycleId) {
        throw new IllegalStateException("recycled multiple times");
    }
    ret.recycleId = 0;
    ret.lastRecycledId = 0;
    this.size = size;
    return ret;
}
对象的回收

可以看到是调用了它的get方法用于对象的获取,那么对象的回收是怎么做的呢?
这里了我们可以看到,这里有个对象类型为DefaultHandle,这个就是其内部接口Handle的实现,这里我们来看下这个对象的实现

static final class DefaultHandle implements Handle {
		 /**
         * 当前的DefaultHandle对象所属的Stack
         */
        private Stack<?> stack;
        /**
         * 真正的对象,value与Handle一一对应
         */
        private Object value;

        DefaultHandle(Stack<?> stack) {
            this.stack = stack;
        }

        public void recycle() {
            Thread thread = Thread.currentThread();
            //如果归还线程是栈归属的线程,就直接push入栈,完成回收
            if (thread == stack.thread) {
                stack.push(this);
                return;
            }
            // we don't want to have a ref to the queue as the value in our weak map
            // so we null it out; to ensure there are no races with restoring it later
            // we impose a memory ordering here (no-op on x86)
            Map<Stack<?>, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get();
            WeakOrderQueue queue = delayedRecycled.get(stack);
            if (queue == null) {
                delayedRecycled.put(stack, queue = new WeakOrderQueue(stack, thread));
            }
            queue.add(this);
        }
    }

DefaultHandle的recycle方法就是负责对象回收的
流程如下:
如果当前线程是当前stack对象的线程,那么将实例放入stack中(存储了对应类型的对象),否则:
获取当前线程对应的Map<Stack, WeakOrderQueue>,并将实例加入到Stack对应的Queue中。

下面看一下上述用的几个组件
Stack的构成

		final Recycler<T> parent;
		//所属线程
        final Thread thread;
        //存储的回收handle及对象的数组
        private DefaultHandle[] elements;
        private final int maxCapacity;
        //栈位置
        private int size;
		
        private volatile WeakOrderQueue head;
        private WeakOrderQueue cursor, prev;

其中看到了WeakOrderQueue对象,来看下对应的构成。

  		// chain of data items
        private Link head, tail;
        // pointer to another queue of delayed items for the same stack
        private WeakOrderQueue next;
        private final WeakReference<Thread> owner;
        private final int id = ID_GENERATOR.getAndIncrement();

Link对象

		private static final class Link extends AtomicInteger {
			//固定大小为16的DefaultHandle数组
            private final DefaultHandle[] elements = new DefaultHandle[LINK_CAPACITY];

            private int readIndex;
            private Link next;
        }

WeakOrderQueue对象中存的就是一个Link构成的链表,当对象放满一个Link后,会在直接新建一个新的Link加入链表,这样做的目的是避免数组扩容带来的性能损耗,直接用链表将数组连接起来
WeakOrderQueue的作用是什么呢?
首先我们的池化对象被回收时,如果是被stack所属线程回收会直接放入stack中,如果是其他线程回收呢?
这里为了避免锁竞争,通过ThreadLocal给其他线程建立一个map,key就是stack本身而value则是WeakOrderQueue,
WeakOrderQueue里面存放的是link数组的链表,每一个数组都是一个DefaultHandle的数组(存放对象)。
另外属于某个stack的WeakOrderQueue会被串联成一个链表,链表的实现就是在创建WeakOrderQueue的时候进行的,如下

		WeakOrderQueue(Stack<?> stack, Thread thread) {
            head = tail = new Link();
            owner = new WeakReference<Thread>(thread);
            synchronized (stack) {
            	//将当前WeakOrderQueue的next指向之前的WeakOrderQueue
            	//然后再将stack指向当前WeakOrderQueue,从而形成了一个链表
                next = stack.head;
                stack.head = this;
            }
        }
整体逻辑结构

在这里插入图片描述

1.当我们调用其get方法的时候逻辑是从ThreadLocal中获取一个stack,该stack内部存储一个DefaultHandle数组,每个DefaultHandle内部都持有一个stack的引用,而每个线程都为一个ThreadLocal存储一个固定的stack。
每个EventLoop线程有一个stack(底层是数组实现),stack里面存储的是DefaultHandle数组,DefaultHandle内部持有我们的ByteBuf对象。当我们想获取一个ByteBuf 就优先从stack里面获取,如果没有则尝试从WeakOrderQueue链表中获取,如果WeakOrderQueue还没有则new一个出来。
2.在回收的时候recyclerHandle的recycle方法会把尝试我们的recyclerHandle放入栈中,如果是被stack所属线程回收则直接放入线程对应的stack中,否则获取当前线程对应的Map<Stack, WeakOrderQueue>,并将池对象加入到Stack对应的WeakOrderQueue中(供后续stack.get()时扫描)。

netty对象池的实现机制很大程度上避免了并发和锁竞争带来的性能损耗,是一种空间换时间的实现方式。


参考:
Netty源码-对象池Recycler

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值