吃透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裏面放着鍵值對,每個回收器回收了其他線程創建的對象時,就會放入對象所對應的StackWeakHashMap中的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有關聯,所以會跟Stackhead結點形成一個單鏈表,頭插法,而且這裏用方法同步,主要是多線程可能同時回收,所以需要同步。
在這裏插入圖片描述

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

再回憶下這張圖:
在這裏插入圖片描述

至此,回收已經講完了,後面我們將獲取。

好了,今天就到這裏了,希望對學習理解有幫助,大神看見勿噴,僅爲自己的學習理解,能力有限,請多包涵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章