對象池化的技術的出現都是能夠說是不得以而爲之,若是咱們有足夠快的CPU,足夠大的內存,那麼對象池化的技術是徹底不必,各類垃圾回收也是不必的;但凡事總有個可是,資源老是有限的,如何在有限資源下發揮出最優效果,也是自人類誕生以來一直在探索的問題。javascript
Tomcat是在Java技術體系中經常使用的Web容器,其採用的NIO(非阻塞I/O)模型相較於傳統的BIO(阻塞I/O)來講得到了更高的性能。其NIO模型以下圖所示。 css
Acceptor用於阻塞的接收鏈接,在接收到鏈接以後選擇一個Poller來執行後續I/O任務處理。(Poller數量是固定的)html
若是是你,你會如何實現這個選擇Poller的過程呢?不妨先考慮一下java
在Tomcat中會將Poller保存在一個環形隊列中,並經過一個原子變量來循環獲取隊列中的下一個元素, 以下圖所示。後端
圖片來自於點擊訪問,本身畫的實在太醜了數組
環形隊列在物理意義上是以線性數組(鏈表亦可)的方式進行保存的,並不是是真的是圓形的方式存在在內存中。瀏覽器
咱們可使用javascript來快速體驗一下環形數組.緩存
let pollers = [1,2,3,4,5,6]
let index = 0
let getNext = function(){
return pollers[Math.abs(index++) % pollers.length]
}
for(let i = 0 ; i < 10086 ; i++){
console.log(getNext())
}
複製代碼
能夠看出環形隊列的實現就在於取餘操做能夠將咱們的索引index
給限制pollers.length
範圍內,使得咱們永遠能夠取到隊列中下一個元素,若是隊列被取完了,則會回到隊列的頭部從新開始遍歷。安全
僅僅如此嗎?在JS中你這樣子玩徹底沒問題,由於瀏覽器的JavaScript是單線程
的執行不會遇到併發問題,但做爲一名後端程序猿,併發以及線程安全
是你必須考慮到。bash
分析代碼能夠發現,咱們須要維護一個索引index
來標誌當前所在的位置,所以若是咱們將index
用原子類保存,這樣就不會遇到線程安全問題也不用加鎖。
所以Poller對象池的實現其實挺簡單的,以下代碼所示,pollerRotater
是一個原子類,能夠保證咱們無鎖,且線程安全的獲取下一個索引(原子類的相關介紹,能夠看這位老哥的文章)
public Poller getPoller0() {
int idx = Math.abs(pollerRotater.incrementAndGet()) % pollers.length;
return pollers[idx];
}
複製代碼
若是你是手頭比較緊的房產投資人,考慮一下如何投資房產才能使效益最大化?一般來講有一下幾種選擇
同理,對於計算機來講內存和CPU都是珍貴的資源,若是你一開始建立了大量的對象,那麼將佔據大量的資源,而且頗有可能這些對象一個都不回被複用而且還會使你的內存溢出,服務崩潰。(固然,若是你服務器內存足夠大,當我沒說)
所以,咱們並能夠回收那些再也不須要用到對象,並保存到咱們對象池中,所以要被回收的對象須要有恢復到最初使的狀態。(租客再也不續租房子了,咱們須要對房子進行清理一遍租給其餘客戶)
此外咱們還能夠對對象進一步細化進行分類,以知足不一樣類型的需求(如單身客戶通常都租單人間,有老婆孩子都會租大一點的)
那麼對象池化技術在Jetty中都使怎麼應用的呢?
咱們知道,不論使用NIO或者BIO都須要提供一個緩衝區以供讀寫數據,而且這些緩衝區會被頻繁的使用到,所以Jetty爲緩衝區設計了一個對象池ByteBufferPool
ArrayByteBufferPool是ByteBufferPool的一個實現
默認狀況下ArrayByteBufferPool的結構以下圖所示
如上圖所示Bucket使用線性數組來保存,每一個Bucket
裝的都是不一樣大小的ByteBuffer
緩衝區,以適應不一樣緩衝區大小需求。默認的有64個Bucket
,ByteBuffer
的基礎大小稱爲Factor
在此圖中factor
的大小爲1024。
爲何要對緩衝區大小進行分類?緣由很簡單,充分利用資源(你讓一個單身漢去租三居室,這不害人嗎,有錢的話,當我沒說)
而且我使用Fiddler簡單統計了一下訪問掘金首頁過程當中常見的數據包大小。
100b
至 400b
之間(說明若是咱們使用jetty的話仍是有優化空間的,如將factor
調整爲512以節省空間)100b
至 1000kb
之間,最大的主要是靜態資源(js、css)等徹底能夠放在CDN上來減輕Web容器的壓力如你所看到,對ByteBuffer按大小進行分類可讓咱們充分利用資源,而且經過調整factor參數來減小內存的佔用來實現進一步的優化。
注意 實際保存
ByteBuffer
的是ConcurrentLinkedDeque
,由於名字太長因此用其接口來表示
那麼,如何根據緩衝區呢大小獲取相應的Bucket,使用如下公式便可
Bucket索引=(目標緩衝區大小 - 1 ) / factor
在本例中,factor是1024, 若是要想要一個10086大小的緩衝區應有
(10086 - 1)/1024 = 10
即Bucekt得數組索引爲10,是Bucekt數組中的第十一個元素其ByteBuffer的大小爲1024*11
至於爲何要將緩衝區大小減一,相信你稍微思考一下便知曉
值得注意是,ArrayByteBufferPool
並不會在一開始就當即爲全部Bucket
分配ByteBufferPool
。而是在須要使用的時候先判斷有沒有目標大小的ByteBuffer
,若是有則從相應的Bucekt中取一個返回給調用方,若是沒有則新建一個 。在不須要使用的時候由調用方主動歸還給ArrayByteBufferPool
除此以外, 還能夠爲ArrayByteBufferPool
指定最大內存(避免耗盡內存形成內存溢出),當緩存的ByteBuffer
的大小總和超過這個值的時候會執行清理工做,將舊的Bucekt
清除掉。
有興趣的能夠閱讀相應類的源碼
org.eclipse.jetty.io.ArrayByteBufferPool
複製代碼
分而治之
能夠說是人類解決問題基本方法論。若是你瞭解ConcurrentHashMap
的分段鎖,那麼你就應該會對Jetty的ByteBufferPool
的設計思想倍感親切,都是分而治之
的思想的最好實踐。