吃透Netty源碼系列三十二之Recycler細節解析一
Recycler
首先他是個抽象類,有個抽象方法,創建對象,也就是說在對象池沒有對象的時候得能創建對象:
protected abstract T newObject(Handle<T> handle);
他與對象池配合使用,比如ObjectPool
中的RecyclerObjectPool
:
private static final class RecyclerObjectPool<T> extends ObjectPool<T> {
private final Recycler<T> recycler;//回收器
RecyclerObjectPool(final ObjectCreator<T> creator) {
recycler = new Recycler<T>() {
@Override
protected T newObject(Handle<T> handle) {
return creator.newObject(handle);
}
};
}
@Override
public T get() {
return recycler.get();
}
}
可見他將具體如果創建對象交給了ObjectCreator
接口:
public interface ObjectCreator<T> {
T newObject(Handle<T> handle);
}
並且封裝了一個對象池的靜態方法,只要傳入創建器即可:
public static <T> ObjectPool<T> newPool(final ObjectCreator<T> creator) {
return new RecyclerObjectPool<T>(ObjectUtil.checkNotNull(creator, "creator"));
}
比如我們的PooledHeapByteBuf
的對象池,只要返回相應的對象就好:
private static final ObjectPool<PooledHeapByteBuf> RECYCLER = ObjectPool.newPool(
new ObjectCreator<PooledHeapByteBuf>() {
@Override
public PooledHeapByteBuf newObject(Handle<PooledHeapByteBuf> handle) {
return new PooledHeapByteBuf(handle, 0);
}
});
一些配置屬性
基本都已經註釋,忘記了可以來看,後面講到的時候會提起,當然這些參數在靜態代碼塊裏可以通過參數設置的方式修改。
private static final AtomicInteger ID_GENERATOR = new AtomicInteger(Integer.MIN_VALUE);//id生成器
private static final int OWN_THREAD_ID = ID_GENERATOR.getAndIncrement();//獲取所屬線程的id
private static final int DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD = 4 * 1024; // Use 4k instances as default.
private static final int DEFAULT_MAX_CAPACITY_PER_THREAD;//每個線程本地變量Stack最大容量,默認4096
private static final int INITIAL_CAPACITY;//Stack初始化容量,默認256
private static final int MAX_SHARED_CAPACITY_FACTOR;//最大共享容量因子,影響WeakOrderQueue的容量,默認2
private static final int MAX_DELAYED_QUEUES_PER_THREAD;//每個線程本地變量WeakHashMap的最大鍵值對個數,默認CPU核心數x2
private static final int LINK_CAPACITY;//鏈接中的數組容量,默認16
private static final int RATIO;//回收間隔,默認8
簡單例子入手
我們直接從獲取和回收開始講好了,這樣比較有針對性。我們以一個簡單的例子入手:
public class RecycleTest {
//創建回收器
private static final Recycler<MyBuff> RECYCLER = new Recycler<MyBuff>() {
@Override
protected MyBuff newObject(Handle<MyBuff> handle) {
return new MyBuff(handle);
}
};
private static class MyBuff {
private final Recycler.Handle<MyBuff> handle;
public MyBuff(Recycler.Handle<MyBuff> handle) {
this.handle = handle;
}
public void recycle() {
handle.recycle(this);
}
}
public static void main(String[] args) {
MyBuff myBuff = RECYCLER.get();
myBuff.recycle();
}
}
這個夠簡單了,創建一個回收器RECYCLER
,一個測試類MyBuff
,然後就是獲取和回收。
Recycler初始化
DELAYED_RECYCLED
先看一些重要的靜態變量的初始化,DELAYED_RECYCLED
是一個線程本地變量,裏面存放的是一個map
,具體類型是WeakHashMap<Stack<?>, WeakOrderQueue>
,map
裏面放着鍵值對,每個回收器回收了其他線程創建的對象時,就會放入對象所對應的Stack
在WeakHashMap
中的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>();//弱鍵回收,鍵如果只有弱引用,可以被GC回收,然後將整個鍵值對回收
}
};
threadLocal
另外一個線程本地變量就是放Stack
的,有初始化,也有安全刪除的方法:
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,
interval, maxDelayedQueuesPerThread);
}
//安全刪除Stack鍵值對
@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);
}
}
}
};
Recycler
的構造函數就不多講了,就是屬性賦值。
Stack構造方法
先說下這個構造方法,後面一些參數會有用到,其實就是一些參數的設置。
Stack(Recycler<T> parent, Thread thread, int maxCapacity, int maxSharedCapacityFactor,
int interval, int maxDelayedQueues) {
this.parent = parent;//回收器
threadRef = new WeakReference<Thread>(thread);//所屬線程的弱引用
this.maxCapacity = maxCapacity;//最大容量,默認4096
availableSharedCapacity = new AtomicInteger(max(maxCapacity / maxSharedCapacityFactor, LINK_CAPACITY));//共享容量,也就是其他線程中的WeakOrderQueue中的最大容量的總和 2048
elements = new DefaultHandle[min(INITIAL_CAPACITY, maxCapacity)];//存放對象的數組,默認256大小
this.interval = interval;//回收間隔
handleRecycleCount = interval; // 間隔計數器,第一個會被回收
this.maxDelayedQueues = maxDelayedQueues;//關聯的WeakOrderQueue最大個數,默認16
}
處理器回收recycle(this)
我們先講回收呢,因爲獲取裏面會涉及到回收後的一些知識,不講回收理解不了的。
@Override
public void recycle(Object object) {
...
Stack<?> stack = this.stack;
...
stack.push(this);//入棧
}
stack.push
這個方法就分兩種情況了:
void push(DefaultHandle<?> item) {
Thread currentThread = Thread.currentThread();
if (threadRef.get() == currentThread) {//屬於棧的線程,直接入棧
pushNow(item);
} else {//不屬於棧的線程或者屬於棧的但是被回收得到線程,需要後面入棧,先放進WeakOrderQueue
pushLater(item, currentThread);
}
}
pushNow當前線程是Stack的所屬線程
如果當前線程是屬於Stack的所屬線程,就調用這個方法,直接將對象放入elements
的數組中。
這個過程還是比較好理解的,首先判斷是否回收過,然後記錄回收信息,判斷回收的數量有沒超過限制,或者是不是丟棄,根據回收間隔。然後看elements
數組是否需要擴容,每次擴容到兩倍,但是不超過最大容量默認4096
。最後把對象放入指定索引的位置。
private void pushNow(DefaultHandle<?> item) {
if ((item.recycleId | item.lastRecycledId) != 0) {//嘗試過回收
throw new IllegalStateException("recycled already");
}
item.recycleId = item.lastRecycledId = OWN_THREAD_ID;//記錄定義的線程ID
int size = this.size;//已有對象數量
if (size >= maxCapacity || dropHandle(item)) {
return;
}
if (size == elements.length) {//要擴容了 每次x2 直到maxCapacity
elements = Arrays.copyOf(elements, min(size << 1, maxCapacity));
}
elements[size] = item;//放入數組中
this.size = size + 1;//個數+1
}
dropHandle
這個就是間隔回收,兩次回收之間隔8
個對象。
boolean dropHandle(DefaultHandle<?> handle) {
if (!handle.hasBeenRecycled) {//沒被回收過
if (handleRecycleCount < interval) {//回收次數小於回收閾值
handleRecycleCount++;//回收次數+1
// Drop the object.
return true;//丟棄
}
handleRecycleCount = 0;//清零
handle.hasBeenRecycled = true;//被回收了
}
return false;
}
這裏容易無解,其實應該是除了第一個直接被回收外,後面每9
個回收1
個。圖示根據回收過來的序號排序從0
開始,綠色表示能被回收,紅色表示被丟棄:
pushLater當前線程不是Stack的所屬線程
這種情況下就是另一個線程來回收,看源碼吧。
private void pushLater(DefaultHandle<?> item, Thread thread) {
...
Map<Stack<?>, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get();//每個線程都會有自己的map
WeakOrderQueue queue = delayedRecycled.get(this);//獲取對應的WeakOrderQueue
if (queue == null) {//不存在嘗試創建一個放入map
if (delayedRecycled.size() >= maxDelayedQueues) {//數量大於閾值 放一個假WeakOrderQueue,丟棄對象
delayedRecycled.put(this, WeakOrderQueue.DUMMY);
return;
}
if ((queue = newWeakOrderQueue(thread)) == null) {//創建一個隊列,如果要分配的容量(16)不夠的話就丟棄對象
// drop object
return;
}
delayedRecycled.put(this, queue);//放入map裏
} else if (queue == WeakOrderQueue.DUMMY) {//如果是假的,就丟棄
// drop object
return;
}
queue.add(item);//放入WeakOrderQueue
}
static final WeakOrderQueue DUMMY = new WeakOrderQueue();
首先我們會獲取線程本地變量WeakHashMap<Stack<?>, WeakOrderQueue>
,然後根據Stack
獲取WeakOrderQueue
。
- 如果獲取不到,說明還沒有這個
Stack
關聯的WeakOrderQueue
被創建。嘗試創建,但是如果WeakHashMap
鍵值對數量超過限制了,就放一個假的WeakOrderQueue
,其實就是一個空的隊列,DUMMY
。否則的話就嘗試創建一個,如果還有分配的容量的話,就創建,並和Stack
一起放入WeakHashMap
中,不行的話就丟棄對象。 - 如果獲取的是
DUMMY
的話,說明WeakHashMap
放滿了,就丟棄。 - 如果獲取到了且不是
DUMMY
就嘗試放隊列裏。
newWeakOrderQueue創建隊列
看看他是如果創建隊列的。
private WeakOrderQueue newWeakOrderQueue(Thread thread) {
return WeakOrderQueue.newQueue(this, thread);
}
static WeakOrderQueue newQueue(Stack<?> stack, Thread thread) {
// 是否可分配鏈接
if (!Head.reserveSpaceForLink(stack.availableSharedCapacity)) {
return null;//分配失敗
}
final WeakOrderQueue queue = new WeakOrderQueue(stack, thread);
stack.setHead(queue);//頭插法,新的隊列插到頭部
return queue;
}
Head鏈接管理者
首先先介紹下Head
類,他管理着裏面所有的鏈接Link
的創建和回收。內部還有一個Link
的連接,其實是單鏈表的表頭,所有的Link
都會被串起來,還有一個容量availableSharedCapacity
,後續的分配和回收都會用到。
Link具體存對象
本身就是原子對象,可以計數,這個在後面放入對象的時候會用到。這個裏面其實就是一個數組,用來存對象,默認容量是16
,還有一個next
指向下一個,至於readIndex
就是獲取對象的時候用,這個跟netty
自定義的ByteBuf
的讀索引類似,表示下一個可獲取對象的索引。
基本就是這樣的結構:
Head.reserveSpaceForLink爲Link申請空間
傳進來的參數是stack.availableSharedCapacity
也就是2048
,說明可以申請的容量是跟這個參數相關的,最多2048
個。也就是說**每個Stack
在其他線程中的回收對象最多是2048
個。**每次分配16個
,如果容量小於16
個了,就不分配了,因此可能導致WeakOrderQueue
創建失敗,丟棄對象。
static boolean reserveSpaceForLink(AtomicInteger availableSharedCapacity) {
for (;;) {
int available = availableSharedCapacity.get();
if (available < LINK_CAPACITY) {//可分配容量小於16 分配失敗
return false;
}
if (availableSharedCapacity.compareAndSet(available, available - LINK_CAPACITY)) {
return true; //分配成功
}
}
}
WeakOrderQueue構造方法
創建一個鏈接Link
,然後給創建一個Head
,並傳入availableSharedCapacity
引用,根據這個availableSharedCapacity
來進行後續Link
的分配和回收的。然後還有個隊尾的引用,同時也存在回收間隔,跟Stack
一樣,默認是8
。
private WeakOrderQueue(Stack<?> stack, Thread thread) {
super(thread);
tail = new Link();//創建鏈接,分配LINK_CAPACITY個DefaultHandle類型的數組
head = new Head(stack.availableSharedCapacity);
head.link = tail;
interval = stack.interval;
handleRecycleCount = interval; // Start at interval so the first one will be recycled.
}
stack.setHead(queue) 設置頭結點
因爲需要跟Stack
有關聯,所以會跟Stack
的head
結點形成一個單鏈表,頭插法,而且這裏用方法同步,主要是多線程可能同時回收,所以需要同步。
queue.add加入隊列
這個也是間隔回收的,從隊尾的Link
開始,看是否滿了,如果滿了就重新創建一個Link
加入鏈表,然後在elements
對應索引位置放入對象,Link
本身就是AtomicInteger
,可以進行索引的改變。
void add(DefaultHandle<?> handle) {
handle.lastRecycledId = id;//記錄上次回收的線程id
if (handleRecycleCount < interval) {//回收次數小於間隔,就丟棄對象,爲了不讓隊列增長過快
handleRecycleCount++;
return;
}
handleRecycleCount = 0;
Link tail = this.tail;
int writeIndex;
if ((writeIndex = tail.get()) == LINK_CAPACITY) {//如果超過鏈接容量限制了
Link link = head.newLink();//創建新的鏈接,如果創建不成功,就返回null,丟棄對象
if (link == null) {
// Drop it.
return;
}
this.tail = tail = tail.next = link;//加入鏈表
writeIndex = tail.get();
}
tail.elements[writeIndex] = handle;//放入對象
handle.stack = null;//放進queue裏就沒有棧了
tail.lazySet(writeIndex + 1);//不需要立即可見,這裏都是單線程操作
}
head.newLink創建鏈接
其實就是前面講過的申請空間,創建Link
。如果成功就創建一個鏈接返回,否則就返回null
。
Link newLink() {
return reserveSpaceForLink(availableSharedCapacity) ? new Link() : null;
}
再回憶下這張圖:
至此,回收已經講完了,後面我們將獲取。
好了,今天就到這裏了,希望對學習理解有幫助,大神看見勿噴,僅爲自己的學習理解,能力有限,請多包涵。