Netty源碼分析之ByteBuf

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

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

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

一、ByteBuf的讀寫操作

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

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

在對象初始化時,readerIndex和writerIndex的值都是0,隨著讀寫操作的進行,readerIndex和writerIndex都會增加,但是readerIndex不會超過writerIndex。當readerIndex大于0時,0-readerIndex之間的空間會被視為discardable(丟棄的空間),discardable會在調(diào)用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],在調(diào)用discardReadBytes后被釋放。

二、ByteBuf源碼分析

[圖片上傳失敗...(image-f16f78-1534303737951)]

2.1 AbstractReferenceCountedByteBuf

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


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內(nèi)存的可見性和CAS操作來保證它的安全性。

retain(): 可以通過調(diào)用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用來解決內(nèi)存泄漏的檢測機制,這里使用了static final,表示所有繼承AbstractByteBuf的類都將共享一個內(nèi)存泄漏管理。

2.3 UnpooledHeapByteBuf

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

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: 用于內(nèi)存分配

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,調(diào)用arraycopy進行內(nèi)存復制,將舊數(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。

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

2.4 UnpooledDirectByteBuf

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

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ū)內(nèi)容寫入到新的緩存區(qū)中。否則需要將readerIndex至 Math.min(writerIndex, newCapacity)的內(nèi)容寫入到新的緩存.

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之后加入內(nèi)存池管理PoolChunk,負責管理內(nèi)存的分配和回收。通過內(nèi)存池管理比之前的ByteBuf性能要好很多。官方說提供了以下優(yōu)勢:

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

有篇文章對使用內(nèi)存池和不使用內(nèi)存池性能作了分析,大家可以看下: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:負責內(nèi)存分配和回收
  • PoolArena:由多個Chunk組成的,而每個Chunk則由多個Page組成
  • PoolSubpage:用于分配小于8k的內(nèi)存,負責把poolChunk的一個page節(jié)點8k內(nèi)存劃分成更小的內(nèi)存段,通過對每個內(nèi)存段的標記與清理標記進行內(nèi)存的分配與釋放。
  • 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ū),在堆之外直接分配內(nèi)存。其繼承自PooledByteBuf,由于PooledByteBuf是基于內(nèi)存池實現(xiàn),所以每次創(chuàng)建字節(jié)緩沖區(qū)的時候不是直接new,而是從內(nèi)存池中去獲取.

參考書籍:Netty in Action

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容