Netty对象池技术Recycler解析

文章目录

        • 1. Recycler是什么?
        • 2. 源码分析
          • 2.1 Stack
          • 2.2 Handle
          • 2.3 WeakOrderQueue
          • 2.4 get获取流程
          • 2.5 recycle 回收流程
        • 3. 总结

1. Recycler是什么?

Recycler是Netty提供的一款轻量级线程局部对象池工具,在前面的文章中也提到过,比如ChannelOutboundBuffer.Entry 、ByteBuf都是基于Recycler的复用来降低大量创建对象带来的消耗。和我们常见的池化技术不太一样,大多数池化技术都是多线程共享,进行并发控制,控制的同时带来了性能上的损耗。Recycler对象池通过ThreadLocal机制消除了多线程的竞争。

举个例子:

public class RecyclerTest {
    private static Recycler<People> peoplePool = new Recycler<People>() {
        @Override
        protected People newObject(Handle<People> handle) {
            return new People(handle);
        }
    };

    public static void main(String[] args) {
        People p1 = peoplePool.get();
        System.out.println(p1);
        p1.recycle();
        People p2 = peoplePool.get();
        System.out.println(p2);
        new Thread(() -> System.out.println(peoplePool.get()))
        .start();
    }
}

class People {
    Recycler.Handle<People> handle;
    public People(Recycler.Handle handle) {
        this.handle = handle;
    }
    public void recycle() {
        handle.recycle(this);
    }
}

输出的结构为:
test.People@3df479 test.People@3df479 test.People@1823a5a
可以看出不同线程使用不同的池。

Recycler内部主要包含三个核心组件,各个组件负责对象池实现的具体部分,Recycler向外部提供统一的对象创建和回收接口:

  1. Handle
    Recycler在内部类中给出了Handle的一个默认实现:DefaultHandle,Handle主要提供一个recycle接口,用于提供对象回收的具体实现,
    每个Handle关联一个value字段,用于存放具体的池化对象,在对象池中,所有的池化对象都被这个Handle包装,Handle是对象池管理的基本单位。
    Handle指向这对应的Stack,Handle存储由Stack维护和管理。
  2. Stack
    Stack具体维护着对象池数据,向Recycler提供push和pop两个主要访问接口,pop用于从内部弹出一个可被重复使用的对象,push用于回收以后可以重复使用的对象。
  3. WeakOrderQueue
    WeakOrderQueue用来暂存待回收的对象,如果recycle()方法回收的对象是当前线程创建的,直接就把对象放到当前线程对应的Stack中。
    如果不是, 则放入WeakOrderQueue中, 当然WeakOrderQueue会和回收的对象所对应的Stack是相关联的。
    在从Stack获取对象池时,如果对象池为空,会尝试从对应的WeakOrderQueue中恢复对象。. WeakOrderQueue可以有多个, 多个WeakOrderQueue是一个链表结构, 可以依次访问.
    Recycler类内部结构如下图,三个核心组件都有对应的实现类。
    Netty对象池技术Recycler解析_第1张图片

2. 源码分析

Recycler是一个抽象类,需要实现newObject()抽象方法,对象池通过调用此方法创建我们需要的对象。回收复用的对象一般是放在Stack的数据结构中。在Recycler有一个属性如下:

private final FastThreadLocal<Stack<T>> threadLocal = new FastThreadLocal<Stack<T>>() {
    @Override
    protected Stack<T> initialValue() {
        return new Stack<T>(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor,
                ratioMask, maxDelayedQueuesPerThread);
    }

    @Override
    protected void onRemoval(Stack<T> value) {
        // Let us remove the WeakOrderQueue from the WeakHashMap directly if its safe to remove some overhead
        if (value.threadRef.get() == Thread.currentThread()) {
           if (DELAYED_RECYCLED.isSet()) {
               DELAYED_RECYCLED.get().remove(value);
           }
        }
    }
};

实现了FastThreadLocal的initialValue和onRemoval方法。initialValue中新建一个Stack对象。

Recycler有一个静态变量每个线程都有自己的Map, WeakOrderQueue>,根据Stack可以获取到对应的WeakOrderQueue

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>();
    }
};
2.1 Stack

每一个线程都有一个Stack对象来保存回收复用的对象,当回收本线程创建的对象时保存至elements数组中,其他线程回收本线程创建的对象时,放在Stack的WeakOrderQueue中。
Stack对象持WeakOrderQueue的引用。有它的属性如下:

static final class Stack<T> {
    final Recycler<T> parent;
    // 该Stack所属的线程,若引用不影响Thread对象的生命周期
    final WeakReference<Thread> threadRef;
    // 可用的共享内存大小,默认为maxCapacity/maxSharedCapacityFactor = 4096/2 = 2048
    // 假设当前的Stack是线程A的,线程B~X等去回收线程A创建的对象时,可回收最多A创建的多少个对象
    // 那么实际上线程A创建的对象最终最多可以被回收maxCapacity + availableSharedCapacity个,默认为6k个
    final AtomicInteger availableSharedCapacity;
    // DELAYED_RECYCLED中最多可存储的{Stack,WeakOrderQueue}键值对个
    final int maxDelayedQueues;
    // elements最大的容量:默认最大为4096
    private final int maxCapacity;
    private final int ratioMask;
    private DefaultHandle<?>[] elements;
    // elements中的元素个数,同时也可作为操作数组的下标
    private int size;
    private int handleRecycleCount = -1; 
    private WeakOrderQueue cursor, prev;
    private volatile WeakOrderQueue head;
}

head指向WeakOrderQueue列表的头部,列表每次都添加至头部,代码如下:

synchronized void setHead(WeakOrderQueue queue) {
    queue.setNext(head);
    head = queue;
}
2.2 Handle

Handle接口中只有一个recycle方法,其实现DefaultHandle:

static final class DefaultHandle<T> implements Handle<T> {
    private int lastRecycledId; // 
    private int recycleId;

    boolean hasBeenRecycled;// 是否被回收

    private Stack<?> stack;// 所属Stack
    private Object value;// 回收的对象

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

    @Override
    public void recycle(Object object) {
        if (object != value) {
            throw new IllegalArgumentException("object does not belong to handle");
        }
        stack.push(this); // 
    }
}
2.3 WeakOrderQueue
private static final class WeakOrderQueue {
    // 队列内部头尾指针
    private Link head, tail; 
    // 指向下一个WeakOrderQueue
    private WeakOrderQueue next;
    // 所属线程
    private final WeakReference<Thread> owner;
    //  WeakOrderQueue的唯一标记
    private final int id = ID_GENERATOR.getAndIncrement();
    private final AtomicInteger availableSharedCapacity;
    // 节点内部为一个数组
    private static final class Link extends AtomicInteger {
        private final DefaultHandle<?>[] elements = new DefaultHandle[LINK_CAPACITY];
        private int readIndex; // 读指针,写指针是AtomicInteger找那个保存的整数
        private Link next;
    }
}

比较有意思的是Link数据结构,继承了AtomicInteger,巧妙的吧AtomicInteger保存的整数当做写指针,这里很有趣,写指针通过CAS可以保证线程安全问题,
但是读指针为什么是普通的int类型变量呢?
说白了读只可能有有一个线程读,也就是所属stack对应的线程,但是写有可能被多个线程同事写。回收即写。

下面看它提供创建对象的方法:

static WeakOrderQueue newQueue(Stack<?> stack, Thread thread) {
    WeakOrderQueue queue = new WeakOrderQueue(stack, thread);
    stack.setHead(queue);// 设置stack head属性
    return queue;
}
private WeakOrderQueue(Stack<?> stack, Thread thread) {
    head = tail = new Link();
    owner = new WeakReference<Thread>(thread);
    availableSharedCapacity = stack.availableSharedCapacity;
}
2.4 get获取流程

get是向外部提供的从对象池获取对象的方法:

public final T get() {
    // 如果maxCapacityPerThread == 0,获取到的对象不具有回收能力
    if (maxCapacityPerThread == 0) {
        // NOOP_HANDLE该对象的recycle(Object object)不做任何事情,即不做回收
        return newObject((Handle<T>) NOOP_HANDLE);
    }
    // 获取当前线程的Stack对象
    Stack<T> stack = threadLocal.get();
    // 从Stack对象中获取DefaultHandle
    DefaultHandle<T> handle = stack.pop();
    if (handle == null) { // 如果为空,则新创建Handle对象,调用我们实现的newObject方法
        handle = stack.newHandle();
        handle.value = newObject(handle);
    }
    return (T) handle.value;
}

stack.pop()实现如下:

 DefaultHandle<T> pop() {
    int size = this.size;
    if (size == 0) {
        if (!scavenge()) {
            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;
}
  1. 如果这个长度是 0,没有元素了,就调用 scavenge 方法尝试从 weakOrderQueue 中转移一些数据到 stack 中
  2. 否则从DefaultHandle数组的尾部出栈,并且置位null,size递减,重置recycleId和lastRecycledId

注意获取的整个流程都不会有多线程竞争的问题。下面看从weakOrderQueue列表中转移对象至Stack的elements数组中。

 boolean scavenge() {
    if (scavengeSome())  return true;  // 如果转移成功 返回true
    prev = null;
    cursor = head;
    return false;
}

boolean scavengeSome() {
    WeakOrderQueue prev;
    WeakOrderQueue cursor = this.cursor;
    if (cursor == null) {
        prev = null;
        cursor = head;
        if (cursor == null)   // 如果head==null,表示当前的Stack对象没有WeakOrderQueue,直接返回
            return false;
    } else  
        prev = this.prev;
    
    boolean success = false;
    do {
        if (cursor.transfer(this)) { // 将 workOrderQueue 的实例转移到 this stack 中
            success = true;
            break;
        }
        WeakOrderQueue next = cursor.next; // 下一个workOrderQueue
        if (cursor.owner.get() == null) {   // 如果创建此workOrderQueue的线程,即回收其他线程创建的对象的线程被回收
            if (cursor.hasFinalData()) { // 如果WeakOrderQueue还有数据
                for (;;) {// 则将此WeakOrderQueue中的数据全部转移
                    if (cursor.transfer(this)) {
                        success = true;
                    } else {
                        break;
                    }
                }
            }
            if (prev != null)   prev.setNext(next); 将此WeakOrderQueue从list中删除
        } else   prev = cursor;
        cursor = next; // 游标下移

    } while (cursor != null && !success);
    this.prev = prev;
    this.cursor = cursor;
    return success;
}

继续跟进真正转移的逻辑WeakOrderQueue#transfer方法:

boolean transfer(Stack<?> dst) {
    Link head = this.head;// 如果head为空 直接返回
    if (head == null)   return false;}

    if (head.readIndex == LINK_CAPACITY) {// Link节点的readIndex索引已经到达该Link对象的DefaultHandle[]的尾部,
        if (head.next == null) { // 如果下一个节点为null 说明已经达到了Link链表尾部,直接返回,
            return false;
        }
        this.head = head = head.next;
    }
    // 获取Link节点的readIndex
    final int srcStart = head.readIndex;
    int srcEnd = head.get(); // 写index,转移的终止下标
    final int srcSize = srcEnd - srcStart;// 可转移的数量
    if (srcSize == 0) return false;
    final int dstSize = dst.size; // Stack的elements长度
    final int expectedCapacity = dstSize + srcSize;// 转移后的长度

    if (expectedCapacity > dst.elements.length) { // 如果大于数组长度
        final int actualCapacity = dst.increaseCapacity(expectedCapacity);// 对目的地Stack进行扩容
        srcEnd = min(srcStart + actualCapacity - dstSize, srcEnd);// 在次计算终止下标
    }
    if (srcStart != srcEnd) {
        final DefaultHandle[] srcElems = head.elements;
        final DefaultHandle[] dstElems = dst.elements;
        int newDstSize = dstSize;
        for (int i = srcStart; i < srcEnd; i++) {
            DefaultHandle element = srcElems[i];
            if (element.recycleId == 0) {
                element.recycleId = element.lastRecycledId;
            } else if (element.recycleId != element.lastRecycledId) {
                throw new IllegalStateException("recycled already");
            }
            srcElems[i] = null;// 置空
            if (dst.dropHandle(element)) continue;// 根据策略判断是抛弃还是回收 
            element.stack = dst; // // 将可转移成功的DefaultHandle元素的stack属性设置为Stack
            dstElems[newDstSize ++] = element;
        }
        if (srcEnd == LINK_CAPACITY && head.next != null) { // head下移,移除之前的Link head
            reclaimSpace(LINK_CAPACITY);
            this.head = head.next;
        }
        head.readIndex = srcEnd;
        if (dst.size == newDstSize) {
            return false;
        }
        dst.size = newDstSize;
        return true;
    } else {
        return false;
    }
}

整体过程较长

2.5 recycle 回收流程

在第一小节中,我们通过调用handle.recycle方法回收对象,其内部的实现也是通过Stack的push方法。

void push(DefaultHandle<?> item) {
    Thread currentThread = Thread.currentThread();
    if (threadRef.get() == currentThread)  pushNow(item);
    else pushLater(item, currentThread);
}

此过程比较直观,如果当前线程和Stack关联的线程相同,调用pushNow方法,也就是将对象回收至Stack内部的数组。否则放入WeakOrderQueue中。
poshNow的实现如下:

private void pushNow(DefaultHandle<?> item) {
    // 开始创建时item.recycleId==0 && item.lastRecycleId==0,回收之后置位非0数字
    if ((item.recycleId | item.lastRecycledId) != 0) {
        throw new IllegalStateException("recycled already");
    }
    item.recycleId = item.lastRecycledId = OWN_THREAD_ID;

    int size = this.size;
    if (size >= maxCapacity || dropHandle(item)) { 
        return;
    }
    if (size == elements.length) {// stack中的elements扩容两倍,复制元素,将新数组赋值给stack.elements 
        elements = Arrays.copyOf(elements, min(size << 1, maxCapacity));
    }
    elements[size] = item;
    this.size = size + 1;
}

为了防止Stack的DefaultHandle[]数组发生爆炸性的增长,所以默认采取每8个元素回收一个,扔掉7个的策略。该策略的在dropHandle中实现。

为了讲解pushLater,举个场景:线程A创建对象people,线程B调用了people的recyle方法,即通过线程B回收people对象。

private void pushLater(DefaultHandle<?> item, Thread thread) {
    // 获取到线程B的delayedRecycled map
    Map<Stack<?>, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get();
    // 查看当前线程中是否含有这个 Stack 对应的队列,this指的是创建对象关联的stack
    WeakOrderQueue queue = delayedRecycled.get(this);
    if (queue == null) {// 如果没有,并且size大于maxDelayedQueues,则将空的WeakOrderQueue 添加进去
        if (delayedRecycled.size() >= maxDelayedQueues) {
            delayedRecycled.put(this, WeakOrderQueue.DUMMY);
            return;
        }
        // 新建一个WeakOrderQueue,如果创建的结果为null,则返回
        if ((queue = WeakOrderQueue.allocate(this, thread)) == null) { 
            return;
        }
        delayedRecycled.put(this, queue); // 放入map中
    } else if (queue == WeakOrderQueue.DUMMY) {
        return;
    }
    queue.add(item);
}

allocate的实现如下,

static WeakOrderQueue allocate(Stack<?> stack, Thread thread) {
    return reserveSpace(stack.availableSharedCapacity, LINK_CAPACITY)
            ? newQueue(stack, thread) : null;
}
// availableSharedCapacity用来限制共享回收对象的数量
private static boolean reserveSpace(AtomicInteger availableSharedCapacity, int space) {
    for (;;) {
        int available = availableSharedCapacity.get();
        if (available < space) {
            return false;
        }
        if (availableSharedCapacity.compareAndSet(available, available - space)) {
            return true; 
        }
    }
}

如果容量够,并且修改成功则返回true。newQueue方法是构建一个新的WeakOrder。queue.add(item)实现如下:

void add(DefaultHandle<?> handle) {
    handle.lastRecycledId = id;// id = ID_GENERATOR.getAndIncrement();
    Link tail = this.tail; // 获取尾部节点
    int writeIndex;
    if ((writeIndex = tail.get()) == LINK_CAPACITY) {  // 如果该节点已被写满了
        if (!reserveSpace(availableSharedCapacity, LINK_CAPACITY)) {// 如果可用的容量不够了
            return;
        }
        // 在tail后新创建一个link节点
        this.tail = tail = tail.next = new Link();
        writeIndex = tail.get();
    }
    tail.elements[writeIndex] = handle;
    
   //如果使用者在将DefaultHandle对象压入队列后, 将Stack设置为null,但是此处的DefaultHandle是持有stack的强引用的,则Stack对象无法回收;
    //而且由于此处DefaultHandle是持有stack的强引用,WeakHashMap中对应stack的WeakOrderQueue也无法被回收掉了,导致内存泄漏。
    handle.stack = null;
    tail.lazySet(writeIndex + 1);
}

3. 总结

整个体系过程比较复杂,一张图总结整个流程:
Netty对象池技术Recycler解析_第2张图片

你可能感兴趣的:(Netty系列)