作用
解決頻繁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中實現對象池維護的方式。