Netty對(duì)象池
在平時(shí)工作中,聽(tīng)說(shuō)和使用過(guò)連接池,線程池等.還有一種就是對(duì)象池,可以實(shí)現(xiàn)對(duì)象復(fù)用的功能.
當(dāng)然實(shí)現(xiàn)對(duì)象池的方式手段有多種,比如有一個(gè)公共的池子,所有需要對(duì)象的線程通過(guò)并發(fā)控制的方式從池子中獲取對(duì)象,并發(fā)控制的同時(shí)伴隨性能的損耗.那么Netty是如何實(shí)現(xiàn)對(duì)象池的呢? 先通過(guò)一段演示代碼說(shuō)起
import io.netty.util.Recycler;public class Book {private String name;private final Recycler.Handle<Book> recyclerHandle;Book(Recycler.Handle<Book> recyclerHandle) {this.recyclerHandle = recyclerHandle;}public void setName(String name) {this.name = name;}void recycle() {recyclerHandle.recycle(this);}}
import io.netty.util.Recycler;import io.netty.util.concurrent.FastThreadLocalThread;import java.util.concurrent.locks.LockSupport;public class Main {private static final Recycler<Book> BOOK = new Recycler<Book>() {protected Book newObject(Handle<Book> handle) {return new Book(handle);}};private static Book book1;private static Book book2;private static Book book3;public static void main(String[] args) throws Exception {new FastThreadLocalThread(() -> {// book1,book2,book3都是從線程Thread-1中創(chuàng)建產(chǎn)生book1 = BOOK.get();book1.setName("Java");book2 = BOOK.get();book2.setName("C");book3 = BOOK.get();book3.setName("C++");// book1在線程Thread-1中回收book1.recycle();new FastThreadLocalThread(() -> {// book2在線程Thread-2中回收book2.recycle();LockSupport.park();// 讓線程不退出,便于觀察}, "Thread-2").start();new FastThreadLocalThread(() -> {// book3在線程Thread-3中回收book3.recycle();LockSupport.park();// 讓線程不退出,便于觀察}, "Thread-3").start();LockSupport.park();// 讓線程不退出,便于觀察}, "Thread-1").start();// 讓線程不退出,便于觀察LockSupport.park();}}
以上代碼,在線程Thread-1中創(chuàng)建了book1(Java),book2(C),book3(C++)這三個(gè)對(duì)象.然后在線程Thread-1中回收book1對(duì)象,在線程Thread-2中回收book2對(duì)象,在線程Thread-3中回收book3對(duì)象.
通過(guò)jps命令查看進(jìn)程ID

通過(guò)jmap命令dump堆內(nèi)存
jmap -dump:format=b,file=2021-heap.hprof 10624
接下來(lái)使用Eclipse的MAT工具打開(kāi)上面創(chuàng)建生成的2021-heap.hprof文件.

點(diǎn)擊上圖中的按鈕,查看線程.

找到線程Thread-1的內(nèi)存地址=0xd786b000
然后根據(jù)這個(gè)地址,查看線程內(nèi)部屬性信息.


線程Thread-1內(nèi)部屬性信息如下圖所示

其中,需要關(guān)注threadLocalMap屬性.

關(guān)于FastThreadLocal的內(nèi)容,之前的文章有介紹.

這里也附著一張F(tuán)astThreadLocal的圖

代碼中創(chuàng)建的book1,book2,book3這三個(gè)對(duì)象,就存放在threadLocalMap屬性里面.依次展開(kāi)它.
首先找到了book1對(duì)象

繼續(xù)找到了book2對(duì)象,而且這個(gè)book2對(duì)象和線程Thread-2有關(guān)系.

最后找到了book3對(duì)象,而且這個(gè)book3對(duì)象和線程Thread-3有關(guān)系.

根據(jù)以上堆數(shù)據(jù)的分析,可以得出如下簡(jiǎn)單關(guān)系.

如上圖,線程Thread-1創(chuàng)建了book1,book2,book3這三個(gè)對(duì)象. 回收之后,book1對(duì)象在線程Thread-1里,book2對(duì)象在線程Thread-2里,book3對(duì)象在線程Thread-3里.
接下來(lái),通過(guò)分析源碼的方式,完善這張圖.
我們先將上面一開(kāi)始的代碼,簡(jiǎn)化下,如下圖所示

根據(jù)上圖,得到如下一個(gè)引用關(guān)系
獲取對(duì)象路徑
Thread -> Recycler -> 目標(biāo)對(duì)象
線程如果需要對(duì)象,不能在像以前那樣手動(dòng)創(chuàng)建對(duì)象(比如new Book()),而是需要借助Recycler對(duì)象,通過(guò)它得到所需要的Book對(duì)象.
因此,Recycler是一個(gè)很重要的類(lèi)
io.netty.util.Recycler在Recycler內(nèi)部有一個(gè)很重要的屬性,如下
private final FastThreadLocal<Stack<T>> threadLocal = new FastThreadLocal<Stack<T>>() {@Overrideprotected Stack<T> initialValue() {return new Stack<T>(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor,interval, maxDelayedQueuesPerThread, delayedQueueInterval);}@Overrideprotected void onRemoval(Stack<T> value) {if (value.threadRef.get() == Thread.currentThread()) {if (DELAYED_RECYCLED.isSet()) {DELAYED_RECYCLED.get().remove(value);}}}};
通過(guò)線程局部對(duì)象FastThreadLocal, 也就是說(shuō), 當(dāng)每個(gè)線程在使用Recycler獲取所需要的對(duì)象的時(shí)候,它的內(nèi)部是實(shí)際上是從每個(gè)線程的Stack中獲取對(duì)象.

小提示
每個(gè)線程都會(huì)有一個(gè)Stack. 當(dāng)然這句話也不是這么絕對(duì), 如果一個(gè)線程它使用了n個(gè)Recycler,那么這個(gè)線程就擁有n個(gè)Stack. 為了說(shuō)明這一點(diǎn),驗(yàn)證代碼如下
如果在一個(gè)線程中,使用多個(gè)Recycler對(duì)象.
import io.netty.util.Recycler;import io.netty.util.concurrent.FastThreadLocalThread;public class Main {private static final Recycler<Book> BOOK_CN = new Recycler<Book>() {protected Book newObject(Handle<Book> handle) {return new Book(handle);}};private static final Recycler<Book> BOOK_EN = new Recycler<Book>() {protected Book newObject(Handle<Book> handle) {return new Book(handle);}};public static void main(String[] args) throws Exception {new FastThreadLocalThread(() -> {// 在同一個(gè)線程中使用2個(gè)Recycler實(shí)例,則會(huì)創(chuàng)建2個(gè)StackBook book1 = BOOK_CN.get();Book book2 = BOOK_EN.get();}, "Thread-1").start();}}

如上圖,線程Thread-1它擁有2個(gè)Stack對(duì)象.
接下來(lái)繼續(xù)分析Netty的對(duì)象池, 代碼中是通過(guò)io.netty.util.Recycler#get方法獲取對(duì)象的,追蹤此方法.
public final T get() {// 如果沒(méi)有啟用線程池,則每次獲取對(duì)象都要新創(chuàng)建對(duì)象.if (maxCapacityPerThread == 0) {return newObject((Handle<T>) NOOP_HANDLE);}// 每個(gè)線程獲取它自己對(duì)應(yīng)的Stack對(duì)象.Stack<T> stack = threadLocal.get();// 從Stack中'彈出'一個(gè)對(duì)象.DefaultHandle<T> handle = stack.pop();if (handle == null) {// 如果Stack中沒(méi)有DefaultHandle對(duì)象,則新創(chuàng)建一個(gè)DefaultHandlehandle = stack.newHandle();// 創(chuàng)建線程需要的那個(gè)對(duì)象handle.value = newObject(handle);}// 返回線程需要的那個(gè)對(duì)象return (T) handle.value;}
根據(jù)以上代碼,得出如下關(guān)系.


Thread -> Recycler -> Stack -> DefaultHandle -> 目標(biāo)對(duì)象

一個(gè)線程,要想得到所需要的目標(biāo)對(duì)象,需要經(jīng)過(guò)Recycler->Stack->DefaultHandle之后,才能拿到目標(biāo)對(duì)象.
之前dump出來(lái)的堆內(nèi)存,也能看到目標(biāo)對(duì)象是'包裹'在DefaultHandle對(duì)象中的.

接下來(lái)看下它是怎么從Stack中'彈出'一個(gè)對(duì)象的.
DefaultHandle<T> pop() {int size = this.size;if (size == 0) {// 這個(gè)地方后面會(huì)說(shuō)if (!scavenge()) {return null;}size = this.size;if (size <= 0) {return null;}}size --;// 從elements數(shù)組中拿出最后一個(gè)元素DefaultHandle ret = elements[size];elements[size] = null;this.size = size;if (ret.lastRecycledId != ret.recycleId) {throw new IllegalStateException("recycled multiple times");}ret.recycleId = 0;ret.lastRecycledId = 0;// 將元素直接返回return ret;}
在Stack內(nèi)部有個(gè)數(shù)組,用來(lái)'裝'DefaultHandle,當(dāng)需要的時(shí)候,直接從這個(gè)數(shù)組中拿出最后一個(gè)元素DefaultHandle返回.
到目前為止,看一下此時(shí)的結(jié)構(gòu)

正所謂'有借有還',既然它是一個(gè)對(duì)象池,當(dāng)使用完之后,需要調(diào)用回收方法. 在文章一開(kāi)始我們自己設(shè)計(jì)的Book類(lèi)中也實(shí)現(xiàn)了回收方法.
public class Book {private String name;private final Recycler.Handle<Book> recyclerHandle;Book(Recycler.Handle<Book> recyclerHandle) {this.recyclerHandle = recyclerHandle;}public void setName(String name) {this.name = name;}// 回收方法void recycle() {// 實(shí)際調(diào)用io.netty.util.Recycler.DefaultHandle#recycle方法recyclerHandle.recycle(this);}}
回收操作就是從這個(gè)io.netty.util.Recycler.DefaultHandle#recycle方法開(kāi)始的.
@Overridepublic void recycle(Object object) {if (object != value) {throw new IllegalArgumentException("object does not belong to handle");}// 得到相應(yīng)的Stack// 每個(gè)DefaultHandle有且僅屬于一個(gè)StackStack<?> stack = this.stack;if (lastRecycledId != recycleId || stack == null) {throw new IllegalStateException("recycled already");}// 將DefaultHandle對(duì)象放入Stack中stack.push(this);}
還要說(shuō)一點(diǎn)是,如下圖,比如線程Thread-1創(chuàng)建一個(gè)book對(duì)象,第一種情況,book對(duì)象使用完之后,最后是由線程Thread-1回收它的(誰(shuí)創(chuàng)建誰(shuí)回收). 第二種情況,book對(duì)象交給了線程Thread-3使用,最后由線程Thread-3回收它(他人創(chuàng)建我來(lái)回收).

根據(jù)上圖,再理解下push源碼
void push(DefaultHandle<?> item) {// 得到當(dāng)前線程Thread currentThread = Thread.currentThread();// 如果當(dāng)前線程和Stack對(duì)應(yīng)的線程是同一個(gè)線程// 每個(gè)Stack有且只屬于一個(gè)線程.if (threadRef.get() == currentThread) {pushNow(item);} else {// 當(dāng)前線程和Stack對(duì)應(yīng)的線程不是同一個(gè)線程pushLater(item, currentThread);}}
將對(duì)象放入Stack分2種情況,第一種是pushNow,第二種是pushLater.
private void pushNow(DefaultHandle<?> item) {if (item.recycleId != 0 || !item.compareAndSetLastRecycledId(0, OWN_THREAD_ID)) {throw new IllegalStateException("recycled already");}item.recycleId = OWN_THREAD_ID;int size = this.size;// dropHandle方法用來(lái)控制放入Stack的速率,有點(diǎn)類(lèi)似流控,稍后再說(shuō)if (size >= maxCapacity || dropHandle(item)) {return;}if (size == elements.length) {elements = Arrays.copyOf(elements, min(size << 1, maxCapacity));}// 直接將元素放入elements數(shù)組的最后elements[size] = item;this.size = size + 1;}
pushNow方法很簡(jiǎn)單,直接將DefaultHandle元素放入到數(shù)組即可.
pushLater方法有點(diǎn)麻煩.首先你要記得,執(zhí)行pushLater的線程一定不是歸屬Stack的線程.
說(shuō)白了,就是由其他線程'協(xié)助'歸屬Stack的線程來(lái)做push操作.
// 1.先將Queue鏈接到鏈表上// 2.將item添加到Queue#Linkprivate void pushLater(DefaultHandle<?> item, Thread thread) {// DELAYED_RECYCLED是FastThreadLocal類(lèi)型, 則每個(gè)線程都有一個(gè)DELAYED_RECYCLED.// 每個(gè)線程會(huì)維護(hù) Stack -> WeakOrderQueue 映射關(guān)系, 即每個(gè)線程會(huì)'協(xié)助'其他線程存儲(chǔ)它的對(duì)象.Map<Stack<?>, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get();WeakOrderQueue queue = delayedRecycled.get(this);if (queue == null) {if (delayedRecycled.size() >= maxDelayedQueues) {delayedRecycled.put(this, WeakOrderQueue.DUMMY);return;}// 創(chuàng)建queue,同時(shí)鏈接到鏈表上if ((queue = WeakOrderQueue.allocate(this, thread)) == null) {// drop objectreturn;}delayedRecycled.put(this, queue);} else if (queue == WeakOrderQueue.DUMMY) {// drop objectreturn;}queue.add(item);}

結(jié)合上圖,再梳理下pushLater邏輯
1.首先線程Thread-3先從Map中查找對(duì)應(yīng)Stack的Queue. 第一次肯定找不到,于是新建一個(gè)Queue,然后再把這個(gè)新建的Queue鏈接到Stack上.
2.將元素添加到Queue.
到了現(xiàn)在,我們可以放一張全局圖了

如上圖,Stack對(duì)象是隸屬于線程thread-1的. 如果從Stack中獲取的對(duì)象,最后也是由線程thread-1回收的,那么對(duì)象就會(huì)存到Stack中的elements數(shù)組中.
如果從Stack中獲取的對(duì)象,最后是由線程thread-2回收,那么thread-2就會(huì)創(chuàng)建一個(gè)針對(duì)此Stack的Queue,鏈接到Queue鏈上.然后再把對(duì)象放到相應(yīng)的Link中的elements數(shù)組中.
還有一點(diǎn),在回收對(duì)象的時(shí)候,并不是'全部都回收'. 而是默認(rèn)每隔8個(gè)回收一個(gè)對(duì)象. 在pushNow和pushLater方法內(nèi)部都會(huì)調(diào)用如下方法
boolean dropHandle(DefaultHandle<?> handle) {// 1.如果當(dāng)前handle之前被回收過(guò),那么此次也會(huì)被回收// 2.如果當(dāng)前handle之前沒(méi)有回收過(guò),那么默認(rèn)每隔8個(gè)回收一個(gè),防止Stack的DefaultHandle[]數(shù)組發(fā)生爆炸性的增長(zhǎng).if (!handle.hasBeenRecycled) {if ((++handleRecycleCount & ratioMask) != 0) {// Drop the object.return true;}handle.hasBeenRecycled = true; // 標(biāo)記元素被回收}return false;}
在從Stack獲取元素的時(shí)候,代碼如下
DefaultHandle<T> pop() {int size = this.size;if (size == 0) {// 如果elements中沒(méi)有可用元素if (!scavenge()) {// 從其他WeakOrderQueue中轉(zhuǎn)移數(shù)據(jù)到當(dāng)前stack#elements中return null;}size = this.size;}size --;DefaultHandle ret = elements[size];elements[size] = null;if (ret.lastRecycledId != ret.recycleId) {throw new IllegalStateException("recycled multiple times");}ret.recycleId = 0;ret.lastRecycledId = 0;this.size = size;return ret;}
默認(rèn)先從Stack自己的elements中獲取元素,如果elements中沒(méi)有元素的時(shí)候,則從Queue鏈上轉(zhuǎn)移數(shù)據(jù)到當(dāng)前Stack的elements數(shù)組中.
分析到這里,我們可以總結(jié)下Netty對(duì)象池的實(shí)現(xiàn)了.
每個(gè)線程都有一個(gè)Stack用于'裝載'需要復(fù)用的對(duì)象. 同時(shí)其他線程也會(huì)'協(xié)助'它回收對(duì)象. 如果Stack中沒(méi)有對(duì)象了,那么會(huì)從其他線程的Queue中轉(zhuǎn)移對(duì)象到Stack中. 如果是Stack隸屬的線程回收對(duì)象,那么對(duì)象會(huì)被放到Stack的elements數(shù)組中,如果是其他線程回收對(duì)象,那么會(huì)把這個(gè)對(duì)象放到其他線程維護(hù)的Queue中.
