??????? 在面試的時候,當面試問到netty的時候問到:你知道jdk nio中的ByteBuffer與netty?中的ByteBuf有什么區(qū)別嗎?來看看面試者的基礎(chǔ)掌握的如何!你能準確回到出來個所以然嗎?
????? 說到j(luò)dk我先說說我身邊使用jdk nio的情況;
????? 我現(xiàn)在公司就有個游戲項目是jdk nio2一行一行實現(xiàn)的通訊架構(gòu),一直在線上運營,目前該架構(gòu)單服承載最高的時候達到3000多人,沒發(fā)現(xiàn)有什么性能瓶頸,當然人數(shù)可能還會繼續(xù)增加,只要提高服務(wù)器配置,或者選擇增加新的服務(wù)器去負載均衡;
?????? 我們都知道jdk nio臭名昭著的epoll bug,它會導(dǎo)致Selector空輪詢,最終導(dǎo)致CPU 100%。官方聲稱在JDK1.6版本的update18修復(fù)了該問題;當然我沒有遇到這個bug,希望后面也不要遇到(祈禱),而netty的設(shè)計就很自然的避免了這個問題;不用擔(dān)心這個bug出現(xiàn);
?????? 這篇文章就是由淺入深解讀ByteBuf家族,從而徹底掌握ByteBuf各知識點。
一、先來比較下jdk nio中的ByteBuffer與netty?中的ByteBuf
JDK NIO 劣勢:
1、ByteBuffer給開發(fā)者的第一感覺就是他的API太少了,程序員的很多需求都不能滿足,只能自己去實現(xiàn)或者做一下封裝;
2、ByteBuffer 長度固定,不能動態(tài)收縮拓展,如果存很長的數(shù)據(jù)時候,就容易越界報錯,例如:
3、ByteBuffer 讀寫操作的時候只有一個索引,操作起來flip(),rewind(),讓人蛋疼,小白操作很容易混亂,這一點Mina IoBuffer和ByteBuffer同病相憐;
相反netty 的ByteBuf解決上面的所有的痛點;豐富的API,支持動態(tài)收縮拓展,并且讀寫索引分開,不用操作字節(jié)的時候,那么蛋疼;
二、ByteBuf實現(xiàn)原理以及如何使用
Netty ByteBuf提供了2個指針變量分別用于順序讀取,和順序?qū)懭?,readerIndex是讀索引,writerIndex是寫索引,二者劃分ByteBuf緩沖區(qū)關(guān)系:
capacity:
?緩沖區(qū)的容量。
readerIndex:
讀取字節(jié),改變readerIndex位置,或者下面的方法去初始化readerIndex
設(shè)置當前讀的位置??梢允褂胷eaderIndex()和readerIndex(int)方法獲取、設(shè)置readerIndex值。每次調(diào)用readXXX方法都會導(dǎo)致readerIndex向writerIndex移動,直到等于writerIndex為止。
writerIndex:
設(shè)置寫的當前位置??梢允褂脀riterIndex()和writerIndex(int)方法獲取、設(shè)置writeIndex的值。每次調(diào)用writeXXX方法都會導(dǎo)致writeIndex向capacity移動,直到等于capacity為止。
discardable bytes:
表示讀取之后丟棄,節(jié)約空間資源。0--readerIndex之間的數(shù)據(jù),?長度是readerIndex - 0,調(diào)用discardReadBytes會丟棄這部分數(shù)據(jù),把readerIndex--writerIndex之間的數(shù)據(jù)移動到ByteBuf的開始位置(0);
使用案例展示:
三、ByteBuf緩沖分類
1、Heap buffer(堆緩沖區(qū)):
就是將數(shù)據(jù)存在JVM堆空間中,在沒有被池化的情況可以快速分配和釋放。
優(yōu)點:由于數(shù)據(jù)是存儲在JVM堆中,因此可以快速的創(chuàng)建與快速的釋放,并且它提供了直接訪問內(nèi)部字節(jié)數(shù)組的方法。
缺點:每次讀寫數(shù)據(jù)時,都需要先將數(shù)據(jù)復(fù)制到直接緩沖區(qū)中再進行網(wǎng)路傳輸。
2、Direct buffer(直接緩沖區(qū)):
直接緩沖區(qū),在堆外直接分配內(nèi)存空間,直接緩沖區(qū)并不會占用堆的容量空間,因為它是由操作系統(tǒng)在本地內(nèi)存進行的數(shù)據(jù)分配。
優(yōu)點:在使用Socket進行數(shù)據(jù)傳遞時,性能非常好,因為數(shù)據(jù)直接位于操作系統(tǒng)的本地內(nèi)存中,所以不需要從JVM將數(shù)據(jù)復(fù)制到直接緩沖區(qū)中 。
缺點:因為Direct Buffer是直接在操作系統(tǒng)內(nèi)存中的,所以內(nèi)存空間的分配與釋放要比堆空間更加復(fù)雜,而且速度要慢一些。
注意:
如果你的數(shù)據(jù)包含在一個在堆上的分配的緩沖區(qū)中,那么事實上,在通過套接字發(fā)送他之前,jvm將會在內(nèi)部把你的緩沖區(qū)復(fù)制到一個直接緩沖區(qū)中;這樣分配釋放就比較浪費資源;
建議:
直接緩沖區(qū)并不支持通過字節(jié)數(shù)組的方式來訪問數(shù)據(jù)。對于后端業(yè)務(wù)的消息編解碼來說,推薦使用HeapByteBuf;對于I/O通信線程在讀寫緩沖區(qū)時,推薦使用DirectByteBuf;
3、Composite Buffer?復(fù)合緩沖區(qū):
可以擁有以上兩種的緩沖區(qū),通過一種聚合視圖來操作底層持有的多種類型Buffer。這種緩沖,jdk nio是沒有這種特性的。
四、源碼解讀ByteBuf家族
先看下ByteBuf的接口和他的三個不同方向的實現(xiàn)抽象類,下面還有具體實現(xiàn)類后面再具體列出來講解,先介紹下這幾個ByteBuf的頂級接口和抽象父類:
(Deprecated?的SwappedByteBuf官方已經(jīng)不贊成去使用了,是不安全緩沖接口)
1、ReferenceCounted:引用計數(shù)器接口。
Netty 4開始,對象的生命周期由它們的引用計數(shù)(reference counts)管理,而不是由垃圾收集器(garbage collector)管理了。ByteBuf是最值得注意的,它使用了引用計數(shù)來改進分配內(nèi)存和釋放內(nèi)存的性能。ByteBuf利用引用計數(shù)來改進分配和回收性能;
? ? ??只要引用計數(shù)大于?0,就能保證對象不會被釋放。當活動引用的數(shù)量減少到0?時,該實例就會被釋放;是由最后訪問(引用計數(shù))對象的那一方來負責(zé)將它釋放。
(1)、如果一個對象實現(xiàn)了ReferenceCounted接口,被初始化的時候,計數(shù)為1。
(2)、retain()方法能夠增加計數(shù),release()?方法能夠減少計數(shù),如果計數(shù)被減少到0則對象會被顯示回收,再次訪問被回收的這些對象將會拋出異常。
(3)、如果一個對象實現(xiàn)了ReferenceCounted,并且包含有其他對象也實現(xiàn)來ReferenceCounted,當這個對象計數(shù)為0被回收的時候,所包含的對象同樣會通過release()釋放掉。
2、Comparable:排序接口。
ByteBuf:定義了一下是否可讀寫的屬性以及定義了一些讀寫操作的抽象接口,供子類繼承實現(xiàn)。
3、WrappedByteBuf:用于裝飾ByteBuf對象,主要有AdvancedLeakAwareByteBuf、SimpleLeakAwareByteBuf和UnreleasableByteBuf三個子類。
(1)、WrappedByteBuf使用裝飾者模式裝飾ByteBuf對象
(2)、AdvancedLeakAwareByteBuf用于對所有操作記錄堆棧信息,方便監(jiān)控內(nèi)存泄漏;
(3)、SimpleLeakAwareByteBuf只記錄order(ByteOrder endianness)的堆棧信息;
(4)、UnreleasableByteBuf用于阻止修改對象引用計數(shù)器refCnt的值。
4、AbstractByteBuf:抽象繼承ByteBuf,?是ByteBuf緩沖的默認實現(xiàn)接口.
子類很多,我們上面說的三種緩沖策略的類實現(xiàn)都是最終繼承實現(xiàn)AbstracByteBuf,而AbstractByteBuf本身并沒有具體去對不同ByteBuf緩沖區(qū)去做具體實現(xiàn),而是由子類去實現(xiàn),原因很簡單父類不知道子類去實現(xiàn)堆內(nèi)存還是直接內(nèi)存,還是復(fù)合緩沖區(qū);只是提供一個抽象接口而已;
再看看AbstractByteBuf源碼中屬性:
定義了讀,寫索引和讀寫索引的標記,以及最大容量,leakDetectro:是監(jiān)測內(nèi)存是否泄漏,是static類型,說明是共享公共的對象;
AbstractByteBuf直接子類AbstractDerivedByteBuf:提供派生ByteBuf的默認實現(xiàn),主要有DuplicatedByteBuf、ReadOnlyByteBuf和SlicedByteBuf。
(1)、DuplicatedByteBuf:使用裝飾者模式創(chuàng)建ByteBuf的復(fù)制對象,使得復(fù)制后的對象與原對象共享緩沖區(qū)的內(nèi)容,但是獨立維護自己的readerIndex和writerIndex。
(2)、ReadOnlyByteBuf:使用裝飾者模式創(chuàng)建ByteBuf的只讀對象,該只讀對象與原對象共享緩沖區(qū)的內(nèi)容,但是獨立維護自己的readerIndex和writerIndex,之后所有的寫操作都被限制;
(3)、SlicedByteBuf:使用裝飾者模式創(chuàng)建ByteBuf的一個子區(qū)域ByteBuf對象,返回的ByteBuf對象與當前ByteBuf對象共享緩沖區(qū)的內(nèi)容,但是維護自己獨立的readerIndex和writerIndex,允許寫操作。
(4)、其實還可以按照另外一個維度去理解,一個是池化的ByteBuf一個是非池化的ByteBuf(用完就銷毀);即PooledByteBuf_ 和UnpooledByteBuf_;
以上三種緩沖官方已經(jīng)Deprecated不推介使用;
這也不推介使用那也不推介使用,那我們用哪些類去操作緩沖呢?
我們用AbstractByteBuf直接子類AbstractReferenceCountedByteBuf,該抽象類的子類實現(xiàn)的緩沖類;
上面我們看到很多實現(xiàn)類,而且沒有一個被Deprecated的;這里只重點介紹幾個常用的;
(1)、UnpooledDirectByteBuf:
?在堆外進行內(nèi)存分配的非內(nèi)存池ByteBuf,內(nèi)部持有ByteBuffer對象,相關(guān)操作委托給ByteBuffer實現(xiàn)。
(2)、UnpooledHeapByteBuf:
? 基于堆內(nèi)存分配非內(nèi)存池ByteBuf,即內(nèi)部持有byte數(shù)組。
(3)、UnpooledUnsafeDirectByteBuf:
? 和另外一個類UnpooledDirectByteBuf差不多相同,區(qū)別在于UnpooledUnsafeDirectByteBuf內(nèi)部使用基于PlatformDependent相關(guān)操作實現(xiàn)ByteBuf,依賴平臺。
(4)、ReadOnlyByteBufferBuf:
? 只讀ByteBuf,內(nèi)部持有ByteBuffer對象,相關(guān)操作委托給ByteBuffer實現(xiàn),該ByteBuf限內(nèi)部使用;
(5)、FixedCompositeByteBuf:
?用于將多個ByteBuf組合在一起,形成一個虛擬的只讀ByteBuf對象,不允許寫入和動態(tài)擴展。內(nèi)部使用Object[]將多個ByteBuf組合在一起,一旦FixedCompositeByteBuf對象構(gòu)建完成,則不會被更改。
(6)、CompositeByteBuf:
?用于將多個ByteBuf組合在一起,形成一個虛擬的ByteBuf對象,支持讀寫和動態(tài)擴展。內(nèi)部使用List組合多個ByteBuf。一般使用使用ByteBufAllocator的compositeBuffer()方法,Unpooled的工廠方法compositeBuffer()或wrappedBuffer(ByteBuf... buffers)創(chuàng)建CompositeByteBuf對象。
(7)、PooledByteBuf:
? 基于內(nèi)存池的ByteBuf,主要為了重用ByteBuf對象,提升內(nèi)存的使用效率;適用于高負載,高并發(fā)的應(yīng)用中。主要有PooledDirectByteBuf,PooledHeapByteBuf,PooledUnsafeDirectByteBuf三個子類,PooledDirectByteBuf是在堆外進行內(nèi)存分配的內(nèi)存池ByteBuf,PooledHeapByteBuf是基于堆內(nèi)存分配內(nèi)存池ByteBuf,PooledUnsafeDirectByteBuf也是在堆外進行內(nèi)存分配的內(nèi)存池ByteBuf,區(qū)別在于PooledUnsafeDirectByteBuf內(nèi)部使用基于PlatformDependent相關(guān)操作實現(xiàn)ByteBuf,具有平臺相關(guān)性。
????? 就到這里了,感謝讀者認真讀完;差不多就netty的ByteBuf家族有了初步的了解了,后面博客我會對三種緩沖(直接緩沖,復(fù)合緩沖,堆緩沖)進行具體源碼剖析,和大家一起學(xué)習(xí)一起探討;如有些的不對的地方,請積極指出,以便及時糾正。