netty 面試 輕量級對象池Recycler原理

本文基于 netty 4.1.46

以下為了方便描述,用戶緩存的對象簡稱 T

一、Recycler 解決的問題

Recycler 即輕量級對象池,避免同一個線程重復創建對象。和異線程回收后的重用問題,例如:線程1創建對象T,線程2回收了對象T,線程1仍然可以通過get方法拿到對象T。
避免重復創建對象的好處是,減少了因創建對象的系統消耗,減輕了jvm yong gc 回收對象的壓力。

注意: 何時用輕量級對象池 Recycler , "通過對象池來避免創建對象并不是一種好的做法,除非池中的對象是非常重量級的,現代的JVM實現具有高度優化的垃圾回收期,其性能很容易就會超過輕量級對象池的性能" 《Effective java》。舉個例子:netty 的 PooledDirectByteBuf 就適合用對象池,因為堆外內存的分配,要系統調用有用戶態和內核態的切換比較耗時,所以非常適用對象池。

二、使用方法

public class RecycleTest {
    private static Recycler<User> RECYCLER = new Recycler<User>() {
        @Override
        protected User newObject(Handle handle) {
            return new User(handle);
        }
    };
    private static class User {
        private final Recycler.Handle<User> handle;
        public User(Recycler.Handle<User> handle) {
            this.handle = handle;
        }
        public void recycle() {
            handle.recycle(this);
        }
    }

    public static void main(String[] args) {
        final User user = RECYCLER.get();
        new Thread(new Runnable() {
            @Override
            public void run() {
                //異線程回收
                user.recycle();
            }
        }).start();
        final User user1 = RECYCLER.get();
        //同線程回收
        user1.recycle();
    }
}

三、Recycler 的實現

1、Recycler 的 uml 圖
clipboard.png
2、Stack 類的結構
clipboard.png

注意:

每一個Recycler對象包含一個FastThreadLocal<Stack<T>> threadLocal實例;每一個線程包含一個Stack對象,該Stack對象包含一個DefaultHandle[],而DefaultHandle中有一個屬性T,用于存儲真實對象。也就是說,每一個T都會被包裝成一個DefaultHandle對象

3、多線程視角描述類的關系
image.png

Recyler類包含一個類對象
static FastThreadLocal<Map<Stack<T>, WeakOrderQueue>>
每一個線程對象包含一個 Map<Stack<T>, WeakOrderQueue>,存儲著當前線程中所有回收其他線程的 T 對象的集合,包括其他線程不同的 Recyler 實例產生的變量產生的 T 或 U 對象,并且分別為其創建的 WeakOrderQueue 對象

WeakOrderQueue 對象中存儲一個以Head為首的Link數組,每個Link對象中存儲一個DefaultHandle[]數組,用于存放回收對象。

說明:(假設線程A創建對象T)

線程 A 回收 T 時,直接將 T 的 DefaultHandle 對象,壓入 Stack的DefaultHandle[] elements 中;

線程 B 回收 T 時,會首先從其 Map<Stack<T>, WeakOrderQueue> 對象中 get('線程A的Stack對象')拿到 WeakOrderQueue,然后直接將T的DefaultHandle對象壓入 WeakOrderQueue 中的Link 鏈表中的尾部 Link 的 DefaultHandle[] 中,同時,這個 WeakOrderQueue 會與線程A的Stack中的 head 屬性進行關聯,用于后續對象的 pop 操作;

當線程 A 從對象池獲取對象時,如果線程A的Stack中有對象,則直接彈出;如果沒有對象,則先從其 head 屬性所指向的 WeakorderQueue 開始遍歷 queue 鏈表,將T對象從其他線程的WeakOrderQueue中轉移到線程A的Stack中(一次pop操作只轉移一個包含了元素的Link),再從 Stack 中彈出。

四、源碼分析

1 、變量對應關系和含義
靜態變量 默認值 Recycler 實例變量 Stack 實例變量 含義
DEFAULT_MAX_CAPACITY_PER_THREAD 4 * 1024 maxCapacityPerThread availableSharedCapacity 當前線程創建的 Handle 對象,被其他線程回收,并且能緩沖到 WeakOrderQueue 中 Handle 的數量
INITIAL_CAPACITY 256 Stack 中 DefaultHandle<?>[] elements 容量
LINK_CAPACITY 16 Link 中的數組 DefaultHandle<?>[] elements 容量
MAX_DELAYED_QUEUES_PER_THREAD 2*cpu maxDelayedQueuesPerThread maxDelayedQueues 當前線程創建的對象能夠讓多少個線程進行回收
8 interval 控制對象回收頻率

2、獲取對象get()

/**
 * 1、獲取當前線程的 Stack
 * 2、從 Stack 里彈出對象
 * 3、創建對象并且和 Stack 綁定
 */
@SuppressWarnings("unchecked")
public final T get() {
    //如果 Stack 的容量為 0
    if (maxCapacityPerThread == 0) {
        return newObject((Handle<T>) NOOP_HANDLE);
    }
    //獲取當前線程的 Stack
    Stack<T> stack = threadLocal.get();
    //從 Stack 里彈出對象
    DefaultHandle<T> handle = stack.pop();
    if (handle == null) {
        //創建對象并且和 Stack 綁定,回收對象時,可以通過 Handle 調用 Stack 進行回收
        handle = stack.newHandle();
        handle.value = newObject(handle);
    }
    return (T) handle.value;
}
2.1、從Stack中取出對象 pop 方法
@SuppressWarnings({ "unchecked", "rawtypes" })
DefaultHandle<T> pop() {
    int size = this.size;
    //當前線程如果沒有對象
    if (size == 0) {
        //試著從其他線程回收的 WeakOrderQueue 對象中取 Handle
        if (!scavenge()) {
            return null;
        }
        size = this.size;
        if (size <= 0) {
            // double check, avoid races
            return null;
        }
    }
    size --;
    DefaultHandle ret = elements[size];
    elements[size] = null;
    // As we already set the element[size] to null we also need to store the updated size before we do
    // any validation. Otherwise we may see a null value when later try to pop again without a new element
    // added before.
    this.size = size;

    if (ret.lastRecycledId != ret.recycleId) {
        throw new IllegalStateException("recycled multiple times");
    }
    ret.recycleId = 0;
    ret.lastRecycledId = 0;
    return ret;
}
2.2 scavengeSome () 方法試著從其他線程回收的 WeakOrderQueue 對象中取 Handle
private boolean scavengeSome() {
    WeakOrderQueue prev;
    WeakOrderQueue cursor = this.cursor;
    if (cursor == null) {
        prev = null;
        cursor = head;
        if (cursor == null) {
            return false;
        }
    } else {
        prev = this.prev;
    }

    boolean success = false;
    do {
        //將 WeakOrderQueue 中的數據拿到 Stack 中的 DefaultHandle<?>[] elements
        if (cursor.transfer(this)) {
            success = true;
            break;
        }//如果沒有拿到

        WeakOrderQueue next = cursor.getNext();
        //cursor 鎖持有的線程 被釋放掉了,那么要做一些清理工作
        if (cursor.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) {
                // Ensure we reclaim all space before dropping the WeakOrderQueue to be GC'ed.
                cursor.reclaimAllSpaceAndUnlink();
                //刪除 cursor 節點
                prev.setNext(next);
            }
        } else {
            //否則移動 prev 指針到下一個節點
            prev = cursor;
        }
        //移動 cursor 指針到下一個節點
        cursor = next;

        //如果已經到結尾了,或者 已經找到了,那么循環退出
    } while (cursor != null && !success);

    this.prev = prev;
    this.cursor = cursor;
    return success;
}
2.3 transfer(this)) 方法;將 WeakOrderQueue 中的數據拿到 Stack 中的 DefaultHandle<?>[] elements
 boolean transfer(Stack<?> dst) {
        Link head = this.head.link;
        //如果當前 Link 中沒有數據
        if (head == null) {
            return false;
        }
        //當前Link中的數據 都沒取走了
        if (head.readIndex == LINK_CAPACITY) {
            //如果 Link 隊列中沒有節點了
            if (head.next == null) {
                return false;
            }
            //將當前 Head 節點移除
            head = head.next;
            this.head.relink(head);
        }
        //head 節點已經被取走的數量
        final int srcStart = head.readIndex;
        //head 節點的 Handle 數量
        int srcEnd = head.get();
        //head 節點可讀的 handle 數量
        final int srcSize = srcEnd - srcStart;
        //head 節點已經沒有可讀 handle 對象了
        if (srcSize == 0) {
            return false;
        }
        //當前 Stack 中 DefaultHandle 的數量
        final int dstSize = dst.size;
        final int expectedCapacity = dstSize + srcSize;
        //head 節點中的 Link 的 Handle 全部讀出來, Stack 中 DefaultHandle[] elements 能否放的下
        if (expectedCapacity > dst.elements.length) {
            final int actualCapacity = dst.increaseCapacity(expectedCapacity);
            //算出 需要數組的最大 下標
            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)) {
                    // Drop the object.
                    continue;
                }
                element.stack = dst;
                dstElems[newDstSize ++] = element;
            }
            // 遍歷下一個節點
            if (srcEnd == LINK_CAPACITY && head.next != null) {
                // Add capacity back as the Link is GCed.
                this.head.relink(head.next);
            }

            head.readIndex = srcEnd;
            if (dst.size == newDstSize) {
                return false;
            }
            dst.size = newDstSize;
            return true;
        } else {
            // The destination stack is full already.
            return false;
        }
    }
}

3、回收對象 recycle()

/**
 * 1、同線程回收對象
 * 2、異線程回收對象
 */
@Override
public void recycle(Object object) {
    if (object != value) {
        throw new IllegalArgumentException("object does not belong to handle");
    }

    //此處的 stack 是創建 Handle 對象的線程持有的 stack
    Stack<?> stack = this.stack;
    if (lastRecycledId != recycleId || stack == null) {
        throw new IllegalStateException("recycled already");
    }

    stack.push(this);
}

void push(DefaultHandle<?> item) {
    Thread currentThread = Thread.currentThread();
    //當前線程是否是創建 stack 的線程
    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);
    }
}
3.1 同線程回收對象
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;
    //判斷存放 handle 數量是否達到上限, 或者
    if (size >= maxCapacity || dropHandle(item)) {
        // Hit the maximum capacity or should drop - drop the possibly youngest object.
        return;
    }
    //如果數組被填充滿了,那么重新創建一個 兩倍大小的數組,上限是maxCapacity
    if (size == elements.length) {
        elements = Arrays.copyOf(elements, min(size << 1, maxCapacity));
    }

    elements[size] = item;
    this.size = size + 1;
}
3.2 異線程回收對象
/**
 * 異線程回收對象
 *
 * 1、獲取 WeakOrderQueue
 * 2、創建 WeakOrderQueue
 * 3、將對象追加到 WeakOrderQueue
 */
private void pushLater(DefaultHandle<?> item, Thread thread) {
    if (maxDelayedQueues == 0) {
        // We don't support recycling across threads and should just drop the item on the floor.
        return;
    }

    // we don't want to have a ref to the queue as the value in our weak map
    // so we null it out; to ensure there are no races with restoring it later
    // we impose a memory ordering here (no-op on x86)
    Map<Stack<?>, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get();
    // this 是指創建 handle 的線程持有的 Stack
    WeakOrderQueue queue = delayedRecycled.get(this);
    //當前線程沒有回收過,創建 handle 的線程所持有 Stack 的 handle
    if (queue == null) {
        //當前線程回收的 Stack 數量是否 達到上限
        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 = newWeakOrderQueue(thread)) == null) {
            // drop object
            return;
        }
        delayedRecycled.put(this, queue);
    } else if (queue == WeakOrderQueue.DUMMY) {
        // drop object
        return;
    }

    queue.add(item);
}

創建 WeakOrderQueue,插入到創建 handle 的線程持有的 Stack 中的 head 在鏈表頭部插入。

static WeakOrderQueue newQueue(Stack<?> stack, Thread thread) {
    // We allocated a Link so reserve the space
    // 被其他線程 緩沖的對象 有沒有超過上限
    if (!Head.reserveSpaceForLink(stack.availableSharedCapacity)) {
        return null;
    }
    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.
    //插入到創建 handle 的線程持有的 Stack 中的 head 在鏈表頭部插入 queue 例如:  queue(當前線程回收的) --> queue1(線程1回收的) --> queue2(線程2回收的)
    stack.setHead(queue);

    return queue;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容