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