netty之對象池個人理解

作用

解決頻繁GC問題

關鍵點

多線程從池中獲取對象,多線程回收對象

思考

假如獲取對象和回收對象的方法上鎖,那麼可以起到池的作用,但顯然不是個好方法,效率太低,得不償失。

如何無鎖化?

每個線程對應一個對象池,並且只有該線程可以獲取對象和回收對象。那麼就可以實現池的無鎖化。
在netty中,採用stack來實現,pop獲取對象,push回收對象。
綁定的方法,顯然是採用ThreadLocal方式,在netty中自行實現了一個FastThreadLocal。
但是,實際問題是,線程A分配了對象,經過一系列傳遞,在線程B中調用回收對象,如何解決?

如何在其他線程中無鎖化回收?

只要不直接操作對象池,而是將回收對象另外存放,類似放到垃圾箱中,先實現對象交還,等A線程有空了,處理掉垃圾箱中的東西,處理的方式,即是挪移到自身的對象池中,以實現複用。
適合實現無鎖化垃圾箱的數據結構即是隊列,因爲隊列的存取不會衝突。在netty中,採用了Recycler.WeakOrderQueue.Link對象,保存着需要回收的對象

何時將垃圾箱中的對象挪到自己的池中來?

當自身的對象池用光的時候,纔將垃圾箱中的對象挪移過來

   Recycler.DefaultHandle<T> pop() {
            int size = this.size;
            if (size == 0) {
                if (!this.scavenge()) {
                    return null;
                }

                size = this.size;
            }
            ……
   }

假設挪移的對象非常多,一次性挪移會耗資源,那麼如何攤還?

攤還對象

在Link之上,netty中採用了WeakOrderQueue來實現攤還。進一步包裝了Link。
每個線程都會構建一個WeakOrderQueue,採用和Thread綁定的Stack對象做索引。
WeakOrderQueue中有一個弱引用對象對Thread進行引用。在引用失效時,會做一次全量的挪移。
從最初的線程對象,可以拿到與其綁定的stack對象,接着可以拿到WeakOrderQueue對象,然後WeakOrderQueue對象就可以拿到Link。將該Link中所有的對象挪移回池中即可。每次挪移只操作1個WeakOrderQueue,然後按照環形的思路,頭指針挪移到下一個。

這樣產生了新的問題,現在已經沒有對象可用的情況下,纔會嘗試挪移,但也只是操作1個Queue,假設騰不出對象怎麼辦?如果一直操作直到找到對象爲止,那麼由於上面將隊列設計成環,在隊列中無對象可用時,就會成爲死循環。

非固定大小的池

池是一個動態維護對象的地方,上面提到的的問題,當queue中騰不出對象時,簡單分配一個新對象,然後繼續頭指針挪到下一隊列元素即可。

那麼,什麼時候回收?

回收方式

通過某種自增計數handleRecycleCount,達到某個設定閾值時,將回收掉。

boolean dropHandle(Recycler.DefaultHandle<?> handle) {
    if (!handle.hasBeenRecycled) {
        if ((++this.handleRecycleCount & this.ratioMask) != 0) {
            return true;
        }
        handle.hasBeenRecycled = true;
    }

    return false;
}

爲何要採用這種方式來回收?回收數又是多少

計數回收

這個問題可能是,new一個小對象時,是在新生代中的,當一個新生代對象熬過15次回收時,會升爲老年代對象,而回收老年代比回收新生代效率低10倍。
在netty中ratioMask的值默認設置爲8。也就是說,池內的對象只能用8次,就回收掉。
具體的深意還不明。

總結

至此,通過一個個的小問題,結合netty中的源碼實現,對逐個問題進行分析解決,最終展開了netty中實現對象池維護的方式。

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