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