Netty源碼分析之ByteBuf(二)

ByteBuf是一個緩沖區(qū)欺嗤,用于和NIO通道進行交互减余。緩沖區(qū)本質上是一塊可以寫入數(shù)據(jù)不脯,然后可以從中讀取數(shù)據(jù)的內存府怯。這塊內存被包裝成NIO Buffer對象,并提供了一組方法防楷,用來方便的訪問該塊內存牺丙。每當你需要傳輸數(shù)據(jù)時,它必須包含一個緩沖區(qū)复局。雖然Java NIO 為我們提供了原生的多種緩沖區(qū)實現(xiàn)冲簿,但是使用起來相當復雜并且沒有經過優(yōu)化,有著以下缺點:

  • 不能進行動態(tài)的增長或者收縮亿昏。如果寫入的數(shù)據(jù)大于緩沖區(qū)capacity的時候峦剔,就會發(fā)生數(shù)組越界錯誤。
  • 只有一個位置標識Position角钩,只能通過flip或者rewind方法來對position進行修改來處理數(shù)據(jù)的存取位置吝沫,一不小心就可能會導致錯誤。

Netty提供了一個強大的緩沖區(qū)ByteBuf递礼,幫助我們解決了以上問題惨险。

一、ByteBuf的讀寫操作

當需要與遠程進行交互時脊髓,需要以字節(jié)碼發(fā)送/接收數(shù)據(jù)辫愉。

ByteBuf有2部分:一個用于讀,一個用于寫将硝。我們可以按順序的讀取數(shù)據(jù)恭朗,并且可以跳到開始重新讀一遍。所有的數(shù)據(jù)操作袋哼,我們只需要做的是調整讀取數(shù)據(jù)索引和再次開始讀操作冀墨。

在對象初始化時,readerIndex和writerIndex的值都是0涛贯,隨著讀寫操作的進行诽嘉,readerIndex和writerIndex都會增加,但是readerIndex不會超過writerIndex弟翘。當readerIndex大于0時虫腋,0-readerIndex之間的空間會被視為discardable(丟棄的空間),discardable會在調用discardReadBytes之后銷毀稀余,同時readerIndex會被重置為0悦冀。

*  BEFORE discardReadBytes()  
*      
*      +-------------------+------------------+------------------+
*      | discardable bytes |  readable bytes  |  writable bytes  |
*      +-------------------+------------------+------------------+
*      |                   |                  |                  |
*      0      <=      readerIndex   <=   writerIndex    <=    capacity
*
*  AFTER discardReadBytes()  
*  
*      +------------------+--------------------------------------+  
*      |  readable bytes  |    writable bytes (got more space)   |  
*      +------------------+--------------------------------------+  
*      |                  |                                      |  
* readerIndex (0) <= writerIndex (decreased)        <=        capacity  

readerIndex: 讀指針,可讀區(qū)域是[readerIndex,writerIndex)

writerIndex: 寫指針,可寫區(qū)域是[writerIndex,capacity)

discardable: 丟棄的讀空間[0,readerIndex],在調用discardReadBytes后被釋放。

二睛琳、ByteBuf源碼分析

1
1

2.1 AbstractReferenceCountedByteBuf

自從Netty 4開始盒蟆,對象的生命周期由它們的引用計數(shù)(reference counts)管理踏烙,而不是由垃圾收集器(garbage collector)管理了。ByteBuf是最值得注意的历等,它使用了引用計數(shù)來改進分配內存和釋放內存的性能讨惩。


private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater;

private volatile int refCnt = 1;

@Override
public ByteBuf retain() {
    for (;;) {
        int refCnt = this.refCnt;
        if (refCnt == 0) {
            throw new IllegalReferenceCountException(0, 1);
        }
        if (refCnt == Integer.MAX_VALUE) {
            throw new IllegalReferenceCountException(Integer.MAX_VALUE, 1);
        }
        if (refCntUpdater.compareAndSet(this, refCnt, refCnt + 1)) {
            break;
        }
    }
    return this;
}

 
public final boolean release() {
    for (;;) {
        int refCnt = this.refCnt;
        if (refCnt == 0) {
            throw new IllegalReferenceCountException(0, -1);
        }

        if (refCntUpdater.compareAndSet(this, refCnt, refCnt - 1)) {
            if (refCnt == 1) {
                deallocate();
                return true;
            }
            return false;
        }
    }
}

refCntUpdater: refCntUpdater對refCnt進行原子更新

refCnt: 每個對象的初始計數(shù)為1,這里利用了voliate內存的可見性和CAS操作來保證它的安全性。

retain(): 可以通過調用retain()增加引用計數(shù)寒屯,前提是引用計數(shù)對象未被銷毀

release(): 當你釋放(release)引用計數(shù)對象時荐捻,它的引用計數(shù)減1.如果引用計數(shù)為1,這個引用計數(shù)對象會被釋放(deallocate)寡夹,并返回對象池

deallocate(): 回收ByteBuf

2.2 AbstractByteBuf

static final ResourceLeakDetector<ByteBuf> leakDetector = new ResourceLeakDetector<ByteBuf>(ByteBuf.class);

    int readerIndex;
    int writerIndex;
    private int markedReaderIndex;
    private int markedWriterIndex;

    private int maxCapacity;

    private SwappedByteBuf swappedBuf;

    protected AbstractByteBuf(int maxCapacity) {
        if (maxCapacity < 0) {
            throw new IllegalArgumentException("maxCapacity: " + maxCapacity + " (expected: >= 0)");
        }
        this.maxCapacity = maxCapacity;
    }

swappedBuf: 大端序列與小端序列的轉換处面。這里有個大小端概念,從網(wǎng)上找了個比較好的例子來解釋大小端菩掏,C魂角,C++蠻多使用小端的,而我們JAVA默認使用大端患蹂。什么意思或颊?比如我要發(fā)一個18,兩個字節(jié)就是0x0012,對于小端模式传于,先發(fā)0x12后發(fā)0x00囱挑,也就是我們先收到12后收到00,對于java,TCP默認的是大端,即先發(fā)高位0x00,后發(fā)0x12沼溜,netty默認大端平挑,即如果按照大端發(fā)送過來的數(shù)據(jù),可直接轉換成對應數(shù)值系草。

leakDetector: leakDetector是Netty用來解決內存泄漏的檢測機制,這里使用了static final通熄,表示所有繼承AbstractByteBuf的類都將共享一個內存泄漏管理。

2.3 UnpooledHeapByteBuf

UnpooledHeapByteBuf是一個非線程池實現(xiàn)的在堆內存進行內存分配的字節(jié)緩沖區(qū)找都,在每次IO操作的都會去創(chuàng)建一個UnpooledHeapByteBuf對象唇辨,如果頻繁地對內存進行分配或者釋放會對性能造成影響。

private final ByteBufAllocator alloc;
private byte[] array;
private ByteBuffer tmpNioBuf;

public ByteBuf capacity(int newCapacity) {
    ensureAccessible();
    if (newCapacity < 0 || newCapacity > maxCapacity()) {
        throw new IllegalArgumentException("newCapacity: " + newCapacity);
    }

    int oldCapacity = array.length;
    if (newCapacity > oldCapacity) {
        byte[] newArray = new byte[newCapacity];
        System.arraycopy(array, 0, newArray, 0, array.length);
        setArray(newArray);
    } else if (newCapacity < oldCapacity) {
        byte[] newArray = new byte[newCapacity];
        int readerIndex = readerIndex();
        if (readerIndex < newCapacity) {
            int writerIndex = writerIndex();
            if (writerIndex > newCapacity) {
                writerIndex(writerIndex = newCapacity);
            }
            System.arraycopy(array, readerIndex, newArray, readerIndex, writerIndex - readerIndex);
        } else {
            setIndex(newCapacity, newCapacity); 
        }
        setArray(newArray);
    }
    return this;
}

ByteBufAllocator: 用于內存分配

array: 字節(jié)數(shù)組作為緩沖區(qū)能耻,用于存儲字節(jié)數(shù)據(jù)

tmpNioBuf: 用來實現(xiàn)Netty ByteBuf 到Nio ByteBuffer的變換

ensureAccessible: 根據(jù)refCnt的值是否為零,判斷引用計數(shù)對象是否被釋放(零是釋放)赏枚。

capacity:

只要newCapacity!=oldCapacity時,都會創(chuàng)建新的數(shù)組作為緩沖區(qū)晓猛,緩沖區(qū)大小是newCapacity饿幅。

如果newCapacity大于oldCapacity,調用arraycopy進行內存復制戒职,將舊數(shù)據(jù)拷貝到新數(shù)組中栗恩,最后使用setArray進行數(shù)組替換。

如果newCapacity小于oldCapacity洪燥,首先查看readerIndex是否小于newCapacity磕秤。

  • readerIndex < newCapacity: 繼續(xù)對writerIndex和newCapacity作比較乳乌,如果writerIndex大于newCapacity的話,就將writerIndex設置為newCapacity亲澡,然后將當前可讀的數(shù)據(jù)拷貝到新的數(shù)組中
  • readerIndex > newCapacity: 沒有新的可讀數(shù)據(jù)要復制到新的字節(jié)數(shù)組緩沖區(qū)中钦扭,只需要把writerIndex跟readerIndex都更新為newCapacity纫版。

最后調用setArray更換字節(jié)數(shù)組.

2.4 UnpooledDirectByteBuf

UnpooledDirectByteBuf是直接緩沖區(qū)床绪,JVM不用將數(shù)據(jù)到堆中,提升了性能其弊。但也有缺點癞己,直接緩沖區(qū)在分配內存和釋放內存時非常復雜,4.X之后Netty使用內存池解決了這樣的問題梭伐。

private final ByteBufAllocator alloc;

private ByteBuffer buffer;
private ByteBuffer tmpNioBuf;
private int capacity;
private boolean doNotFree;

@Override
public ByteBuf capacity(int newCapacity) {
    ensureAccessible();
    if (newCapacity < 0 || newCapacity > maxCapacity()) {
        throw new IllegalArgumentException("newCapacity: " + newCapacity);
    }

    int readerIndex = readerIndex();
    int writerIndex = writerIndex();

    int oldCapacity = capacity;
    if (newCapacity > oldCapacity) {
       //舊緩沖區(qū)存儲空間不足時痹雅,新建一個緩存區(qū),然后將舊緩存區(qū)的數(shù)據(jù)全部寫入到新的緩存區(qū)糊识,然后釋放舊的緩存區(qū)绩社。
        ByteBuffer oldBuffer = buffer;
        ByteBuffer newBuffer = allocateDirect(newCapacity);
        oldBuffer.position(0).limit(oldBuffer.capacity());
        newBuffer.position(0).limit(oldBuffer.capacity());
        newBuffer.put(oldBuffer);
        newBuffer.clear();
        setByteBuffer(newBuffer);
    } else if (newCapacity < oldCapacity) {
        ByteBuffer oldBuffer = buffer;
        ByteBuffer newBuffer = allocateDirect(newCapacity);
        if (readerIndex < newCapacity) {
            if (writerIndex > newCapacity) {
                writerIndex(writerIndex = newCapacity);
            }
            oldBuffer.position(readerIndex).limit(writerIndex);
            newBuffer.position(readerIndex).limit(writerIndex);
            newBuffer.put(oldBuffer);
            newBuffer.clear();
        } else {
            setIndex(newCapacity, newCapacity);
        }
        setByteBuffer(newBuffer);
    }
    return this;
}

private void setByteBuffer(ByteBuffer buffer) {
    ByteBuffer oldBuffer = this.buffer;
    if (oldBuffer != null) {
        if (doNotFree) {
            doNotFree = false;
        } else {
            freeDirect(oldBuffer);
        }
    }

    this.buffer = buffer;
    tmpNioBuf = null;
    capacity = buffer.remaining();
}

allocateDirect: 在堆外創(chuàng)建一個大小為newCapacity的新緩沖區(qū)

newCapacity > oldCapacity: 將舊緩沖區(qū)的數(shù)據(jù)全部寫入到新的緩存區(qū)并且釋放舊的緩沖區(qū)

newCapacity < oldCapacity : 壓縮緩沖區(qū),如果readerIndex > newCapacity,無需將舊的緩存區(qū)內容寫入到新的緩存區(qū)中赂苗。否則需要將readerIndex至 Math.min(writerIndex, newCapacity)的內容寫入到新的緩存.

position:

  • 當你寫數(shù)據(jù)到Buffer中時愉耙,position表示當前的位置。初始的position值為0.當一個byte拌滋、long等數(shù)據(jù)寫到Buffer后朴沿, position會向前移動到下一個可插入數(shù)據(jù)的Buffer單元。position最大可為capacity – 1.
  • 當讀取數(shù)據(jù)時败砂,也是從某個特定位置讀赌渣。當將Buffer從寫模式切換到讀模式,position會被重置為0. 當從Buffer的position處讀取數(shù)據(jù)時昌犹,position向前移動到下一個可讀的位置坚芜。

limit:

  • 在寫模式下,Buffer的limit表示你最多能往Buffer里寫多少數(shù)據(jù)斜姥。寫模式下鸿竖,limit等于Buffer的capacity。
  • 當切換Buffer到讀模式時疾渴, limit表示你最多能讀到多少數(shù)據(jù)千贯。因此,當切換Buffer到讀模式時搞坝,limit會被設置成寫模式下的position值搔谴。換句話說,你能讀到之前寫入的所有數(shù)據(jù)(limit被設置成已寫數(shù)據(jù)的數(shù)量桩撮,這個值在寫模式下就是position)

setByteBuffer: 釋放舊的緩沖區(qū)然后將buffer指向新的緩沖區(qū)

2.5 PooledByteBuf

在Netty4之后加入內存池管理PoolChunk敦第,負責管理內存的分配和回收峰弹。通過內存池管理比之前的ByteBuf性能要好很多。官方說提供了以下優(yōu)勢:

  • 頻繁分配芜果、釋放buffer時減少了GC壓力鞠呈;
  • 在初始化新buffer時減少內存帶寬消耗(初始化時不可避免的要給buffer數(shù)組賦初始值);
  • 及時的釋放direct buffer右钾。

有篇文章對使用內存池和不使用內存池性能作了分析蚁吝,大家可以看下:Netty4底層用對象池和不用對象池實踐優(yōu)化

abstract class PooledByteBuf<T> extends AbstractReferenceCountedByteBuf {
    protected PoolChunk<T> chunk;
}
final class PoolChunk<T> {

    final PoolArena<T> arena;
    private final PoolSubpage<T>[] subpages;
    PoolChunkList<T> parent;
}

PooledByteBuf主要由以下幾個部分組成:

  • PoolChunk:負責內存分配和回收
  • PoolArena:由多個Chunk組成的,而每個Chunk則由多個Page組成
  • PoolSubpage:用于分配小于8k的內存舀射,負責把poolChunk的一個page節(jié)點8k內存劃分成更小的內存段窘茁,通過對每個內存段的標記與清理標記進行內存的分配與釋放。
  • PoolChunkList:負責管理多個chunk的生命周期

2.6 PooledDirectByteBuf

private static final Recycler<PooledDirectByteBuf> RECYCLER = new Recycler<PooledDirectByteBuf>() {
    @Override
    protected PooledDirectByteBuf newObject(Handle handle) {
        return new PooledDirectByteBuf(handle, 0);
    }
};

static PooledDirectByteBuf newInstance(int maxCapacity) {
    PooledDirectByteBuf buf = RECYCLER.get();
    buf.setRefCnt(1);
    buf.maxCapacity(maxCapacity);
    return buf;
}

PooledDirectByteBuf是直接緩沖區(qū)脆烟,在堆之外直接分配內存山林。其繼承自PooledByteBuf,由于PooledByteBuf是基于內存池實現(xiàn)邢羔,所以每次創(chuàng)建字節(jié)緩沖區(qū)的時候不是直接new驼抹,而是從內存池中去獲取.

參考書籍:Netty in Action

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拜鹤,隨后出現(xiàn)的幾起案子框冀,更是在濱河造成了極大的恐慌,老刑警劉巖署惯,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件左驾,死亡現(xiàn)場離奇詭異,居然都是意外死亡极谊,警方通過查閱死者的電腦和手機诡右,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轻猖,“玉大人帆吻,你說我怎么就攤上這事×撸” “怎么了猜煮?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長败许。 經常有香客問我王带,道長,這世上最難降的妖魔是什么市殷? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任愕撰,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘搞挣。我一直安慰自己带迟,他們只是感情好,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布囱桨。 她就那樣靜靜地躺著仓犬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舍肠。 梳的紋絲不亂的頭發(fā)上搀继,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音貌夕,去河邊找鬼律歼。 笑死,一個胖子當著我的面吹牛啡专,可吹牛的內容都是我干的。 我是一名探鬼主播制圈,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼们童,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鲸鹦?” 一聲冷哼從身側響起慧库,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎馋嗜,沒想到半個月后齐板,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡葛菇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年甘磨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眯停。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡济舆,死狀恐怖,靈堂內的尸體忽然破棺而出莺债,到底是詐尸還是另有隱情滋觉,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布齐邦,位于F島的核電站椎侠,受9級特大地震影響,放射性物質發(fā)生泄漏措拇。R本人自食惡果不足惜我纪,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宣羊,春花似錦璧诵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至苛坚,卻和暖如春比被,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泼舱。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工等缀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人娇昙。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓尺迂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親冒掌。 傳聞我的和親對象是個殘疾皇子噪裕,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內容