Netty Recycler的源码分析

Recycler分析


调用来源:PooledByteBuf.java


涉及的知识:

  • AtomicInteger
  • WeakReference
  • ThreadLocal

在DefaultHandle 中调用的recycle只是把需要回收的对象装在一个栈中,那么问题转化为如何这个栈是如何回收的.

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

        private 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);
        }
    }



那么回收的方法应该和stack有关.


在介绍栈之前先了解下WeakOrderQueue,顾名思义,它是弱有序队列.
WeakOrderQueue是由链表实现,其中存储的数据是DefaultHanle类型的数组.值得注意的是虽然类名由弱引用字样,但是属性中并没有弱引用字段.
该类中的所有构造方法都是private类型的,获取实例只能通过newQueue方法获取:

private WeakOrderQueue(Stack<?> stack, Thread thread) {
    head = tail = new Link();
    owner = new WeakReference<Thread>(thread);

    // Its important that we not store the Stack itself in the WeakOrderQueue as the Stack also is used in
    // the WeakHashMap as key. So just store the enclosed AtomicInteger which should allow to have the
    // Stack itself GCed.
    availableSharedCapacity = stack.availableSharedCapacity;
}
static WeakOrderQueue newQueue(Stack<?> stack, Thread thread) {
            WeakOrderQueue queue = new WeakOrderQueue(stack, thread);
            // Done outside of the constructor to ensure WeakOrderQueue.this does not escape the constructor and so
            // may be accessed while its still constructed.
            stack.setHead(queue);
            return queue;
  }


可以看到该方法除了构造并返回WeakOrderQueue实例外还将该实例放入了一个Stack实例中的容器中.
该容器在类中的属性是head,数据结构是用链表实现的栈.(原因)
其中WeakOrderQueue包含一个Link内部类,该内部类继承了AtomicInteger,含有一个存放DefaultHandle类型的数组属性.继承的AtomicInteger用来记录数组的下标.如代码:

private static final class Link extends AtomicInteger {
            private final DefaultHandle<?>[] elements = new DefaultHandle[LINK_CAPACITY];

            private int readIndex;
            private Link next;
        }

其中Link的作用我认为是空间分配与释放的基本单位,当一个Link元素填满时,需要重新生成一个Link实例并将其置入链表末尾.释放的时候在transfer方法中释放.

transfer方法的作用:当Stack没有元素可以提供给消费者时,transfer方法将WeakOrderQueue中的元素传送给该Stack.该行为由Stack主动发起,所以在Stack中才会有一个WeakOrderQueue的链表,Stack在这些链表元素里获取消费者需要的数据.而当一个Link完全被转化后它的引用会被撤销,从而等待被GC


Stack之中的存放的元素是DefaultHandle类型的实例,并且存有一个head属性存放WeakOrderQueue.
Stack是外界唯一可访问Recycler类中存储的handle元素.当自身实例不足时,会主动发起transfer方法从WeakOrderQueue中获取.消费者获取元素的代码如下:

//类Recycler
public final T get() {
        if (maxCapacityPerThread == 0) {
            return newObject((Handle<T>) NOOP_HANDLE);
        }
        Stack<T> stack = threadLocal.get();
        DefaultHandle<T> handle = stack.pop();
        if (handle == null) {
            handle = stack.newHandle();
            handle.value = newObject(handle);
        }
        return (T) handle.value;
    }

//内部类Stack
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;bei
    ret.lastRecycledId = 0;
    this.size = size;
    return ret;
}


其中:

  • 如果stack所代指的池中没有实例,则会new一个Object返回.
  • threadLocal解决了线程同步的问题,不过netty并没有使用JDK自带的ThreadLocal,而是自己定义了一个叫FastThreadLocal的类实现的,感兴趣的可以看看源码.
  • 每次成功pop一个实例就会将它的recycleId和lastRecycledId置为0,保证下回可再次回收.

注意这行代码的意义

if (ret.lastRecycledId != ret.recycleId) {
    throw new IllegalStateException("recycled multiple times");
}

我认为是这样的:Stack返回给消费者的实例必然是没有其他消费者在使用的实例,也就是说在Stack中的实例都是没有其他消费者使用的.而这两个值什么时候不相等呢,如果一个实例本身在Stack中,已经回收了,在这种状态下再次被回收后就会出现两值不相等的情况.为什么避免这种情况呢,那是因为这样同一个实例的引用会被放在Stack或者WeakOrderQueue的两个位置,当一个消费者拿走一个引用时,另一个引用所指向的实例已经不在符合'没有人使用'这条规则,所以要避免这种情况.

Stack的push方法:pushNow 和 pushLater

void push(DefaultHandle<?> item) {
            Thread currentThread = Thread.currentThread();
            if (threadRef.get() == currentThread) {
                // The current Thread is the thread that belongs to the Stack, we can try to push the object now.
                pushNow(item);
            } else {
                // The current Thread is not the one that belongs to the Stack
                // (or the Thread that belonged to the Stack was collected already), we need to signal that the push
                // happens later.
                pushLater(item, currentThread);
            }
        }

简单地说pushNow是将回收元素放在Stack的元素列表中;pushLater则是放在WeakOrderQueue的元素列表中.
存放在哪取决于Stack中的线程引用是否和当前线程相同.于是这里会看出一个优先级,Stack更趋向于给自己引用线程提供可利用元素.

借用网上一张图总结一下:

  • 消费者调用Recyler的get方法,即调用Stack的pop方法获取池中元素
  • 如果Stack中元素不足,调用transfer方法,触发scavenge方法,然后从Link中的WeakOrderQueue中获取元素.放到Stack中.
  • 调用Handle的recycle方法进行回收,即调用Stack的push方法将回收元素放在相应位置.Recycler中页有recycle方法,但是已经被@Deprecated.
posted @ 2018-02-22 18:05  fengbs  阅读(437)  评论(0编辑  收藏  举报