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源碼分析

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