1、对象池的背景
Netty本身作为通信用的框架,消息的创建和处理十分频繁,大多数的消息本身可循环使用的程度不高,大部分都是创建完传输后就被等待GC了。但是考虑到大部分消息只是消息部分内容有所差异,可以考虑对创建消息的对象进行循环利用,减少创建和GC带来的消耗。
2、Recycler图例
简单介绍以下,每个Recycler都会有将内部的Stack存储在InternalThreadLocalMap之中,并且将对象存储在Stack中,所以Stack是每个线程专属的,Recycler更可以说是一个用于访问Stack的窗口,类似ThreadLocal。Stack内部数据主要存储在DefaultHandle中,每个DefaultHandle负责存储一个对象。同时Stack的有一个叫head的WeakOrderQueue的属性,负责指向其他线程为该Stack创建的WeakOrderQueue,只要有其他线程为该Stack创建WeakOrderQueue,就会存储到前一个WeakOrderQueue的next域之中,以此形成一个由WeakOrderQueue组成的链。同样的,WeakOrderQueue本身也不存储数据,而是交给其内部类Link存储,Link内部也是由DefaultHandler组成的数组,并且通过next组成Link的单向链表。
对于每一个线程创建的对象,如果是其自身回收的,就放在Stack的DefaultHandle之中;如果是其他线程为其回收,则该线程会创建一个属于该Stack的WeakOrderQueue,并将回收的对象放入其中的Link,以及将WeakOrderQueue加入链路中。
对于Recycler源码的解析,要分4个部分说明
- 本线程回收自己创建的对象
- 本线程为其他线程回收对象
- 从Stack中直接获取对象进行复用
- 从WeakOrderQueue的Link中搬迁对象进行复用
3、本线程回收自己创建的对象
从Recycler中获取一个对象,直接调用其get方法即可,该方法返回前面定义的一个泛型T。
Recycler.get();
该get方法会尝试从线程本地变量获取Stack,再从中提取出一个DefaultHandle加以复用。
public final T get() {
/* 从Recycler中获取一个元素T */
/* 如果maxCapacityPerThread值为0,那么每次都创建一个新的对象 */
/* maxCapacity表示Stack能够存储的DefaultHandle最大数量*/
if (maxCapacityPerThread == 0) {
return newObject((Handle<T>) NOOP_HANDLE);
}
/*
* fastThreadLocal的值获取是懒加载的,所以第一次会出初始化一个stack
*/
Stack<T> stack = threadLocal.get();
/* 从stakc中pop一个DefaultHandle */
DefaultHandle<T> handle = stack.pop();
if (handle == null) {
handle = stack.newHandle();
handle.value = newObject(handle);
}
return (T) handle.value;
}
其中maxCapacityPerThread表示每个线程中Stack可以容纳的DefaultHandle的数量,因为在Stack中,DefaultHandle是以数组的形式存储的。
static final class Stack<T> {
...
private DefaultHandle<?>[] elements;
...
}
所以必须在使用前指定该数组的初始化容量,该值如果不指定时默认为4096
public abstract class Recycler<T> {
...
// 这里设定默认每个线程容量为4096
private static final int DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD = 4 * 1024;
int maxCapacityPerThread = SystemPropertyUtil.getInt("io.netty.recycler.maxCapacityPerThread",
SystemPropertyUtil.getInt("io.netty.recycler.maxCapacity", DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD));
if (maxCapacityPerThread < 0) {
maxCapacityPerThread = DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD;
}
DEFAULT_MAX_CAPACITY_PER_THREAD = maxCapacityPerThread;
...
// 每个线程默认容量
private final int maxCapacityPerThread;
private final int maxSharedCapacityFactor;
private final int ratioMask;
private final int maxDelayedQueuesPerThread;
// Stack的获取采用懒加载的机制,所以会因第一次获取不到而尝试调用初始化方法
private final FastThreadLocal<Stack<T>> threadLocal = new FastThreadLocal<Stack<T>>() {
// 在初始化的时候将maxCapacityPerThread注入到Stack中
@Override
protected Stack<T> initialValue() {
return new Stack<T>(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor,
ratioMask, maxDelayedQueuesPerThread);
}
...
}
}
Mark: 可以通过设置参数-Dio.netty.recycler.maxCapacityPerThread 方式设置每个Stack的DefaultHandle的容量。
回到Recycler.get方法,可以发现在maxCapacityPerThread的值为0时,表示不会回收对象,每次都创建一个新的对象返回给调用方。
public final T get() {
/* 从Recycler中获取一个元素T */
/* 如果maxCapacityPerThread值为0,那么每次都创建一个新的对象 */
/* maxCapacity表示Stack能够存储的DefaultHandle最大数量*/
if (maxCapacityPerThread == 0) {
return newObject((Handle<T>) NOOP_HANDLE);
}
/*
* fastThreadLocal的值获取是懒加载的,所以第一次会出初始化一个stack
*/
Stack<T> stack = threadLocal.get();
/* 从stakc中pop一个DefaultHandle */
DefaultHandle<T> handle = stack.pop();
if (handle == null) {
handle = stack.newHandle();
handle.value = newObject(handle);
}
return (T) handle.value;
}
从本地变量获取到Stack,类似栈的方法调用pop方法返回一个DefaultHandle,如果该DefaultHandle为null,则创建一个新的DefaultHandle返回给调用方,注意因为是从Recycler中获取对象复用,所以不用重新放回到Stack中,Stack中的对象应该表示为回收后待复用的。在看pop方法前,我们先来看创建DefaultHandle的两个相关方法,Stack.newHandle()和newObject()。
// DefaultHandle类定义
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<?> stack = this.stack;
// https://github.com/netty/netty/issues/8220
if (lastRecycledId != recycleId || stack == null) {
throw new IllegalStateException("recycled already");
}
stack.push(this);
}
}
// newHandle()方法定义
DefaultHandle<T> newHandle() {
return new DefaultHandle<T>(this);
}
// newObject()方法由继承了Recycler的类默认实现,下面已Entry类为例
static final class Entry {
private static final Recycler<Entry> RECYCLER = new Recycler<Entry>() {
@Override
protected Entry newObject(Handle<Entry> handle) {
return new Entry(handle);
}
};
...
}
每个DefaultHandler有保留创建它的Stack的引用,所以可以从Stack找到DefaultHandle的同时,也可以从DefaultHandle找到Stack,这点在回收DefaultHandle的对象时十分方便。再者每个继承了Recycler的对象仅仅需要实现方法newObject用于没有对象可复用时创建新对象,内部逻辑通通在Recycler实现了。这两点在实现某个功能池的十分有利。
下面重点介绍Stack.pop方法,该方法作用主要时用于从Stack中返回对象,其中当DefaultHandle存在可复用对象时直接返回,以及没有可复用对象时从WeakOrderQueue搬迁对象过来。本节假设Stack无需从WeakOrderQueue获取对象时处理。
DefaultHandle<T> pop() {
// this.size是Stack的容量
int size = this.size;
// 如果当前stack没有存储任何的值
if (size == 0) {
// scavenge返回一个DefaultHandler
if (!scavenge()) {
return null;
}
size = this.size;
if (size <= 0) {
// double check, avoid races
return null;
}
}
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;
}
当Stack.DefaultHandle存在可复用的对象时,这个比较简单,直接根据当前DefaultHandle的最后一个对象返回复用,设置size--同时,将DefaultHandle[size]的位置设为null,避免重复占坑。对于lastRecycleId和recycleId两个值的理解,会在回收对象时给出。目前知道从Stack.pop()返回DefaultHandle时,会将该DefaultHandle这两个值同时设定为0。
4、线程从WeakOrderQueue中迁移对象进行回收
在pop的另一个分支中,如果当前Stack的size为0,就会尝试从其他地方获取对象进行回收。
现在我们还没看到线程是如何帮其他线程回收对象,所以先简单介绍下,线程会为预留一部分空间用于存储为其他线程回收的对象。这部分数据结构如下
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>();
}
};
每个线程会在自身本地变量(FastThreadLocal)这里存储一个Map<Stack<?>,WeakOrderQueue>类型,名为DELAYED_RECYCLED的对象,其中key为当前线程为了哪些线程回收了对象,就会把这些线程对应的Stack作为key放入到DELAYED_RECYCLED中(每个回收的DefaultHandle都有引用指向Stack,所以可以从这里获取到Stack的引用)。具体的WeakOrderQueue这用于存储回收的对象。WeakOrderQueue的数据结构如下
private static final class WeakOrderQueue {
static final WeakOrderQueue DUMMY = new WeakOrderQueue();
@SuppressWarnings("serial")
static final class Link extends AtomicInteger {
private final DefaultHandle<?>[] elements = new DefaultHandle[LINK_CAPACITY];
private int readIndex;
Link next;
}
static final class Head {
private final AtomicInteger availableSharedCapacity;
Link link;
Head(AtomicInteger availableSharedCapacity) {
this.availableSharedCapacity = availableSharedCapacity;
}
...
}
private final Head head;
private Link 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();
WeakOrderQueue内部有一个Link的对象,这里才是实际存储为其他线程回收对象的地方,而且每个Link有默认大小为LINK_CAPACITY,当一个Link不够存储时,交给下一个Link存储,Link由此组成单向链表。关于WeakOrderQueue空间内部大小的定义如下
MAX_SHARED_CAPACITY_FACTOR = max(2,
SystemPropertyUtil.getInt("io.netty.recycler.maxSharedCapacityFactor",
2));
MAX_DELAYED_QUEUES_PER_THREAD = max(0,
SystemPropertyUtil.getInt("io.netty.recycler.maxDelayedQueuesPerThread",
// We use the same value as default EventLoop number
NettyRuntime.availableProcessors() * 2));
LINK_CAPACITY = safeFindNextPositivePowerOfTwo(
max(SystemPropertyUtil.getInt("io.netty.recycler.linkCapacity", 16), 16));
- MAX_SHARED_CAPACITY_FACTOR,表示为其他线程预留的空间(Link链表可以存储数据的总大小,注意不是单个Link)和自身回收对象(Stack可以存储数据大小)直接的比值,默认是1:2,则Link链表最终可以存储 4096 / 2 = 2048个对象,注意是为每个其他线程存储2048个对象;
- MAX_DELAYED_QUEUES_PER_THREAD,表示每个线程可以在DELAYED_RECYCLED中存储多少个Stack,换句话说,每个线程只能为MAX_DELAYED_QUEUES_PER_THREAD个线程回收对象;
- LINK_CAPACITY,表示每个Link能够存储的DefaultHandle的数量,默认为16个,超出这个范围将创建新的Link,所有Link加起来的数量不能超过MAX_SHARED_CAPACITY_FACTOR。
除此之外,WeakOrderQueue本身也是一个链表,内部存储了next指针用于存储下一个WeakOrderQueue的地址引用。
private static final class WeakOrderQueue {
...
private WeakOrderQueue next;
...
}
而在Stack中会存储第一个为本线程回收对象时创建的WeakOrderQueue
static final class Stack<T> {
...
private WeakOrderQueue cursor, prev;
private volatile WeakOrderQueue head;
...
}
这样就可以在Stack中通过head域遍历其他线程为本线程回收的对象WeakOrderQueue。
回到pop方法中,会先获取当前Stack的size(int size = this.size;),如果这个size的值为0,则表示当前Stack的DefaultHandle没有对象可以复用,需要调用scavenge获取由其他线程为本线程回收的对象进行复用。
boolean scavenge() {
// continue an existing scavenge, if any
if (scavengeSome()) {
return true;
}
// reset our scavenge cursor
prev = null;
cursor = head;
return false;
}
boolean scavengeSome() {
WeakOrderQueue prev;
// this.cursor一开始为null,表示上一次没有对象可回收时遍历在哪个WeakOrderQueue
WeakOrderQueue cursor = this.cursor;
// cursor为null,说明该线程是第一次遍历WeakOrderQueue
if (cursor == null) {
// prev 不是null就是cursor的上一个WeakOrderQueue
prev = null;
// head是Stack中存储的指向第一个WeakOrderQueue
cursor = head;
// 如果cursor为空,那么head就为空,相当于没有其他线程为当前线程收集了对象
// Map<Stack<?>, WeakOrderQueue>中key值为当前线程的Stack的value不存在
// 所以这里返回false
if (cursor == null) {
return false;
}
} else {
// 如果cursor不为空的情况,prev指向this.prev,也就是上一次遍历到哪个WeakOrderQueue的再上一个WeakOrderQueue
prev = this.prev;
}
boolean success = false;
// cursor表示上一次遍历的WeakOrderQueue的是哪一个
do {
// 假设第一次进来,所以cursor应该为head,所以相当于this.head.transfer
// WeakOrderQueue.transfer(Stack)返回false表示失败
if (cursor.transfer(this)) {
success = true;
break;
}
// 走到这里说明搬迁失败了,需要去下一个WeakOrderQueue获取
WeakOrderQueue next = cursor.next;
if (cursor.owner.get() == null) {
// If the thread associated with the queue is gone, unlink it, after
// performing a volatile read to confirm there is no data left to collect.
// We never unlink the first queue, as we don't want to synchronize on updating the head.
//如果与队列相关联的线程不见了,请在执行易失性读取以确认没有剩余数据要收集之后将其取消链接。
// 我们永远不会取消链接第一个队列,因为我们不想在更新头部时进行同步。
// WeakOrderQueue的线程并不是表示Stack的线程,而是帮忙回收Stack数据的线程,有可能线程已经结束了
// 导致上面的owner为null,毕竟是WeakReference
// cursor.hasFinalData() 是判断最后一个link的是否有数据
if (cursor.hasFinalData()) {
for (;;) {
if (cursor.transfer(this)) {
success = true;
} else {
break;
}
}
}
if (prev != null) {
prev.setNext(next);
}
} else {
prev = cursor;
}
// 从下一个WeakOrderQueue开始重新遍历
cursor = next;
} while (cursor != null && !success);
// prev表示cursor的上一个
this.prev = prev;
this.cursor = cursor;
return success;
}
// 用于从WeakOrderQueue搬迁对象到Stack.DefaultHandle方法
boolean transfer(Stack<?> dst) {
// 获取第一个WeakOrderQueue的head.link的值,这里记位link-first
Link head = this.head.link;
// 如果一个link为空直接退出,去查找下一个线程为本线程创建的WeakOrderQueue
if (head == null) {
return false;
}
// 第一次进来readIndex应该为0,不能与link的最大容量LINK_CAPACITY
if (head.readIndex == LINK_CAPACITY) {
// 如果订阅LINK_CAPACITY,说明当前link已经检索玩没有可回收的对象了
// 直接判断下一个是否为空,如果为空,返回null说明检索完了该WeakOrderQueue的值
if (head.next == null) {
return false;
}
// 交给GC去回收当前link,将WeakOrder.head.link指向下一个link
this.head.link = head = head.next;
// 因为当前link已经没用并且交给GC去回收了,所以调用reclaimSpace修改可申请回收对象大小
this.head.reclaimSpace(LINK_CAPACITY);
}
// 走到这一步,link-first要么的readIndex不等于LINK_CAPACITY,
// 要么就是link-first等于LINK_CAPACITY,所以进入link-first.next,也就是下一个
// 记为link-second,如果link-secnod不为null,那么必然有对象可以被回收利用
// 所以readIndex表示上一次回收的游标+1,也就是这次回收开始的地方,当它等于LINK_CAPACITY
// 时,就表示没有对象可以被回收,默认应该是从0开始,因为是一个数组
final int srcStart = head.readIndex;
// srcEnd暂时不太清除,猜测是当前link内部存储的DefaultHandler的当前数组size,因为link本身是继承
// AtomicInteger的,所以只需要用内置的方法get和add原子存储
int srcEnd = head.get();
// 判断数组的容量大小,如果为0则没法回收返回false
final int srcSize = srcEnd - srcStart;
if (srcSize == 0) {
return false;
}
// dst是stack当前数量大小
final int dstSize = dst.size;
// 计算当前stack的数量大小 + link的存储数量大小
final int expectedCapacity = dstSize + srcSize;
// java数组中,length表述数组可存储容量,size表示当前数组已经存储的数据
// 这里表示当前stack的容量 + 即将从link转移过来的数量会不会超过当前stack的DefaultHandler可以
// 容纳的数据量
// 如果需要的数据容量,大于当前Stack的DefaultHandler当前数组的最大容量继续扩容
if (expectedCapacity > dst.elements.length) {
// 返回的是扩容后数组数组总大小(包括已经有数组的部分3)
final int actualCapacity = dst.increaseCapacity(expectedCapacity);
// 这段的意思是,actualCapacity 是扩容后数组数组总大小,减去dstSize 就是剩余可存储的容量
// 加上stcStart就表示原来link中,从srcStart触发能挖走读少数据,并且取最小值
// 这个srcEnd也不能操作原来head的结束位置,所以取两者的最小值。
srcEnd = min(srcStart + actualCapacity - dstSize, srcEnd);
}
// 上面扩容好了数组,表示有位置存储了,就应该开始搬迁了
if (srcStart != srcEnd) {
// srcElems 要搬数据地方
final DefaultHandle[] srcElems = head.elements;
// dstElems 要放入数据的地方
final DefaultHandle[] dstElems = dst.elements;
// newDstSize = dstSize就是dstElems 当前数据数量
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)) {
// Drop the object.
continue;
}
/* 这段需要研究下 */
element.stack = dst;
dstElems[newDstSize ++] = element;
}
// 如果当前link的全部数据被回收了,那么就回收可用容量,并且link直接丢弃去gc
if (srcEnd == LINK_CAPACITY && head.next != null) {
// Add capacity back as the Link is GCed.
this.head.reclaimSpace(LINK_CAPACITY);
this.head.link = head.next;
}
// 这里readIndex等于srcEnd,保留这一次回收的点,下一次从srcEnd开始回收
head.readIndex = srcEnd;
// 当前stack的大小等于原来大小,表示没搬迁成功,返回false
if (dst.size == newDstSize) {
return false;
}
// 修改stack的DefaultHandler的大小
dst.size = newDstSize;
return true;
} else {
// The destination stack is full already.
return false;
}
}
// 因为DefaultHandler是一个数组不像list可以无限存储
// 所以需要提前考虑数组的容量是否来的及存储
int increaseCapacity(int expectedCapacity) {
// 当前DefaultHandler的大小存储
int newCapacity = elements.length;
// 最大存储,应该是4k
int maxCapacity = this.maxCapacity;
do {
// 扩容1倍
newCapacity <<= 1;
// 只要Stack的DefaultHandler小于目标的容量,而且必须在maxCapacity(4096内)
} while (newCapacity < expectedCapacity && newCapacity < maxCapacity);
// 判断新的容量大小是否超过最大限制程度
newCapacity = min(newCapacity, maxCapacity);
// 复制数组的数据
if (newCapacity != elements.length) {
elements = Arrays.copyOf(elements, newCapacity);
}
return newCapacity;
}
5、本线程回收自身创建的对象
在讲解回收对象时,先来看看如何使用Recycler提供的对象回收Demo
import io.netty.util.Recycler;
class User {
private String name;
private Recycler.Handle<User> handle;
public User(String name, Recycler.Handle<User> handle) {
this.name = name;
this.handle = handle;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Recycler.Handle<User> getHandle() {
return handle;
}
public void setHandle(Recycler.Handle<User> handle) {
this.handle = handle;
}
}
public class UserRecycler extends Recycler<User> {
@Override
protected User newObject(Handle<User> handle) {
return new User("theone", handle);
}
public static void main(String[] args) {
UserRecycler recycler = new UserRecycler();
User user = recycler.get();
}
}
当你使用Recycler提供的功能去回收一个对象的时候,该对象需要保存容器DefaultHandle,以便在不需要时可以回收对象。例如
user.getHandle().recycle(user);
回收一个对象的方法放在DefaultHandle实现中,该方法为继承Handle接口所必须实现的
public interface Handle<T> {
void recycle(T object);
}
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<?> stack = this.stack;
// https://github.com/netty/netty/issues/8220
if (lastRecycledId != recycleId || stack == null) {
throw new IllegalStateException("recycled already");
}
stack.push(this);
}
}
所以可以简单得到一个结论,DefaultHandle为实际对象的容器,负责保存对象以及回收对象,每个线程有一个Stack,用于保存回收的对象DefaultHandle数组。
从DefaultHandl.Recycle这个Handle的默认实现来看,其通过DefaultHandle获取Stack域,然后将自身塞回去Stack的DefaultHandle的数组中。
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);
}
}
Stack中会存储创建该Stack的线程引用threadRef,所以通过判断存储的线程引用和当前线程Thread.currentThread就可以判断当前是在给自己回收对象还是为其他线程回收对象,这里我们先看前者pushNow(),为自己回收线程。
private void pushNow(DefaultHandle<?> item) {
if ((item.recycleId | item.lastRecycledId) != 0) {
throw new IllegalStateException("recycled already");
}
item.recycleId = item.lastRecycledId = OWN_THREAD_ID;
int size = this.size;
// dropHandle 为true表示需要抛弃这个对象,避免Stack每一个都回收导致爆炸性增长
if (size >= maxCapacity || dropHandle(item)) {
// Hit the maximum capacity or should drop - drop the possibly youngest object.
return;
}
if (size == elements.length) {
elements = Arrays.copyOf(elements, min(size << 1, maxCapacity));
}
elements[size] = item;
this.size = size + 1;
}
//防止Stack的DefaultHandle[]数组发生爆炸性的增长,所以默认采取每8个元素回收一个,扔掉7个的策略),如果不需要回收,直接返回
boolean dropHandle(DefaultHandle<?> handle) {
if (!handle.hasBeenRecycled) {
if ((++handleRecycleCount & ratioMask) != 0) {
// Drop the object.
return true;
}
// hasBeenRecycled 仅仅用于标记是否被回收
handle.hasBeenRecycled = true;
}
return false;
}
为自己回收线程比较简单,只要判断自身Stack的DefaultHandle数组是否还有空间回收即可,或者调用dropHandle()判断是否需要回收。具体流程如下
- 获取当前Stack中DefaultHandle数组的大小,如果超过maxCapacity(默认为4096,也就是DEFAULT_MAX_CAPACITY_PER_THREAD),那么可以返回不会收该对象;
- 调用dropHandle(),判断当前Stack回收的对象次数是否是ratioMask的整数倍,如果不是则不会受该对象,避免回收太快,导致Stack中DefaultHandle数组爆炸性增长;
- 如果没有超过数组最大容量,dropHandle()判断也需要回收对象,那么判断当前数组是否可以存放该回收对象,否则则需要扩容数组,默认是扩容1倍(size << 1)。
6、本线程回收其他线程回收的对象
如果线程回收的对像发现是其他线程创建的,则需要考虑帮忙回收。这里我们来看pushLater的实现。
private void pushLater(DefaultHandle<?> item, Thread thread) {
// 从FastThreadLocal从获取到 Map<Stack<?>, WeakOrderQueue>
Map<Stack<?>, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get();
WeakOrderQueue queue = delayedRecycled.get(this);
// 如果queue为空,那么则当前线程中没有存储当前需要回收的线程的stack
if (queue == null) {
// 注意这里DELAYED_RECYCLED本身也是有最大值的,没法再帮忙插入更多线程的stakc,所以需要抛弃这个
if (delayedRecycled.size() >= maxDelayedQueues) {
// Add a dummy queue so we know we should drop the object
delayedRecycled.put(this, WeakOrderQueue.DUMMY);
return;
}
// 分配一个队列
// Check if we already reached the maximum number of delayed queues and if we can allocate at all.
if ((queue = WeakOrderQueue.allocate(this, thread)) == null) {
// drop object
return;
}
delayedRecycled.put(this, queue);
} else if (queue == WeakOrderQueue.DUMMY) {
// drop object
return;
}
// DefaultHandler不是放在WeakOrderQueue中,而是WeakOrderQueue.Head.Link中,所以这里的add要进去看下
queue.add(item);
}
DELAYED_RECYCLED是一个FastThreadLocal类型,这种相当于是一个窗口用于获取存储在线程中的数据。在pushLaster方法中直接获取Map<Stack<?>, WeakOrderQueue>,该Map存储了当前线程帮其他线程回收的对象集合。这里当初把我看迷糊了一分钟是在于下一句delayedRecycled.get(this)。
前面说了DELAYED_RECYCLED是以Stack为key作为存储的Map,虽然Stack由每个线程创建,但是其他线程是可以访问到这个Stack的。就在这里回收的时候,如果发现当前回收的DefaultHandle对应的Stack不是当前回收线程创建的,那么就会触发去DELAYED_RECYCLED查找key = Stack的WeakOrderQueue,并尝试将其放入。
如果从Stack获取到的WeakOrderQueue为空,那么表示当前回收线程没有回收该创建Stack的线程的对象。所以需要判断是否由足够的空间用于回收。每个线程最多能够为maxDelayedQueue个Stack回收。
if (delayedRecycled.size() >= maxDelayedQueues) {
// Add a dummy queue so we know we should drop the object
delayedRecycled.put(this, WeakOrderQueue.DUMMY);
return;
}
这里如果判断超出了maxDelayedQueues的限制,那么就会插入一个WearOrderQueue.DUMMY,表示我们需要直接丢弃这个待回收的对象。相反,如果没超出则分配一个队列,并加入当前线程的Map<Stack<?>,WearOrderQueue>中。
if ((queue = WeakOrderQueue.allocate(this, thread)) == null) {
// drop object
return;
}
delayedRecycled.put(this, queue);
分配一个队列采用的是WeakOrderQueue的一个静态方法
static WeakOrderQueue allocate(Stack<?> stack, Thread thread) {
// We allocated a Link so reserve the space
return Head.reserveSpace(stack.availableSharedCapacity, LINK_CAPACITY)
? newQueue(stack, thread) : null;
}
// recerseSpace作用只是扣除空间的容量,没有创建队列
static boolean reserveSpace(AtomicInteger availableSharedCapacity, int space) {
assert space >= 0;
for (;;) {
// 获取当前线程为其他线程准备的可用回收空间大小,然后扣除space的大小
int available = availableSharedCapacity.get();
if (available < space) {
return false;
}
if (availableSharedCapacity.compareAndSet(available, available - space)) {
return true;
}
}
}
static WeakOrderQueue newQueue(Stack<?> stack, Thread thread) {
final 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;
}
// Marked as synchronized to ensure this is serialized.
synchronized void setHead(WeakOrderQueue queue) {
queue.setNext(head);
head = queue;
}
private void setNext(WeakOrderQueue next) {
assert next != this;
this.next = next;
}
这里创建新队列的同时,会把刚创建的队列加入到带回收的DefaultHandle对应的Stack的head域中,目的是为了方便在复用对象的时候遍历。同时为了避免多个线程同时创建,用Synchronized修饰setHead()方法。
private volatile WeakOrderQueue head;
7、Recycler对象回收和创建流程图
8、关于在Stack中使用WeakReference<Thread>,而不是直接存储Thread
除了在Stack中使用弱引用之外,WeakOrderQueue中本身存储的是WeakReference。
// 在Stack中
final WeakReference<Thread> threadRef;
// 在WeakOrderQueue中
private final WeakReference<Thread> owner;
这里使用弱引用的原因在于,避免外部的线程被回收时,当前线程还保留着这个对象的引用。这不仅会导致外部线程没有被回收,而且也无法回收外部线程占用在Map<Stack<?>, WearOrderQueue>的一部分空间。所以这里采用弱引用,当外部线程被回收时,无论时Stack还是WeakOrderQueue中的引用都会为null,就可以通过这点判断是否可以丢弃这部分空间的占用。这点在复用对象时体现逻辑在Stack.scavengeSome()中
WeakOrderQueue next = cursor.next;
if (cursor.owner.get() == null) {
// If the thread associated with the queue is gone, unlink it, after
// performing a volatile read to confirm there is no data left to collect.
// We never unlink the first queue, as we don't want to synchronize on updating the head.
if (cursor.hasFinalData()) {
for (;;) {
if (cursor.transfer(this)) {
success = true;
} else {
break;
}
}
}
if (prev != null) {
prev.setNext(next);
}
}
上面代码的逻辑是,判断当前遍历的WeakOrderQueue对应的Owner(线程)是否为null,是的话就尝试搬空内部的对象,然后在下面的if判断如果prev != null,就通过setNext方法丢弃该cursor。所以我们也知道了在Stack中存储prev的作用,它表示上一次遍历的cursor的前一个指针,当cursor为null,就可以通过访问prev引用丢弃这个cursor。
这里由一个小疑问? 如果当前线程要回收的Stack对应的线程被回收了,根据push的逻辑会走pushLater的分支,这会不会造成空间浪费?
void push(DefaultHandle<?> item) {
Thread currentThread = Thread.currentThread();
// 如果Stack对应的线程被回收,这里threadRef.get()返回的是null,所以走pushLater分支
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);
}
}
// DefaultHandle是需要回收的,thread表示当前线程
private void pushLater(DefaultHandle<?> item, Thread thread) {
Map<Stack<?>, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get();
WeakOrderQueue queue = delayedRecycled.get(this);
// 如果回收线程没有回收关于这个item的Stack,所以这里应该会出现queue为null
// 当空间足够时,会为一个null的线程调用allocate的方法创建WeakOrderQueue
if (queue == null) {
if (delayedRecycled.size() >= maxDelayedQueues) {
// Add a dummy queue so we know we should drop the object
delayedRecycled.put(this, WeakOrderQueue.DUMMY);
return;
}
if ((queue = WeakOrderQueue.allocate(this, thread)) == null) {
return;
}
delayedRecycled.put(this, queue);
} else if (queue == WeakOrderQueue.DUMMY) {
return;
}
queue.add(item);
}
9、LastRecycleId和recycleId
这两个是出现在DefaultHandle的属性
static final class DefaultHandle<T> implements Handle<T> {
private int lastRecycledId;
private int recycleId;
...
}
初始化一个DefaultHandle时,这两个值为0
DefaultHandle(Stack<?> stack) {
this.stack = stack;
}
在pushNow的时候,这两个值会被赋予同一个值
item.recycleId = item.lastRecycledId = OWN_THREAD_ID;
而在复用对象时候,在pop方法成功获取到一个DefaultHandle时,设置两个值同时0
DefaultHandle<T> pop() {
...
ret.recycleId = 0;
ret.lastRecycledId = 0;
...
}
而在其他线程帮忙回收时,在pushLater最后调用add方法设置为回收的WeakOrderQueue的id
void add(DefaultHandle<?> handle) {
// 将当前DefaultHandler最后一个回收的id设置为当前回收的WeakOrderQueue
handle.lastRecycledId = id;
...
}
看完赋值,我们再看使用场景,在DefaultHandle.recycler方法中会尝试判断lastRecycleId是否等于recycleId,不是则表示回收多次。
if (lastRecycledId != recycleId || stack == null) {
throw new IllegalStateException("recycled already");
}
同样在transfer时候,也有过判断DefaultHandle是否被回收过
else if (element.recycleId != element.lastRecycledId) {
throw new IllegalStateException("recycled already");
}
所以可以做个大胆的猜测,lastRecycleId和 recycleId用于判断这个DefaultHandle是否被回收多次,具体如下:
- 当一个DefaultHandle被创建或者从对象池中返回(pop方法)时,这两个值就会为0;
- 当DefaultHandle被内部Stack对应的线程,也就是创建这个DefaultHandle的线程回收时,这两个值也会保持一致,只不过是Recycle的一个域OWN_THREAD_ID (OWN_THREAD_ID = ID_GENERATOR.getAndIncrement());
- 当DefaultHandle被外部线程回收时,只会把lastRecycledId设置为WeakOrderQueue对应的ID;
- 所以可以看出,无论时被pushNow还是pushLater,始终都会让lastRecycledId和recycledId的值不会为0,这样就表示被回收过;
- 而初始化或者从pop返回时,为0表示还未被回收;
- 你会看到判断lastRecycledId != recycleId 的逻辑出现在DefaultHandle.recycle()、transfer以及在pushNow中,这是因为如果出现不一致,那么就是被外面的线程通过pushLater回收了,避免重复回收。