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向外部提供统一的对象创建和回收接口:
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
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>();
}
};
每一个线程都有一个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;
}
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); //
}
}
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;
}
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;
}
注意获取的整个流程都不会有多线程竞争的问题。下面看从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;
}
}
整体过程较长
在第一小节中,我们通过调用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);
}