ByteBuf
ByteBuf是什么
為了平衡數(shù)據(jù)傳輸時(shí)CPU與各種IO設(shè)備速度的差異性,計(jì)算機(jī)設(shè)計(jì)者引入了緩沖區(qū)這一重要抽象纱烘。jdkNIO庫提供了java.nio.Buffer接口杨拐,并且提供了7種默認(rèn)實(shí)現(xiàn),常見的實(shí)現(xiàn)類為ByteBuffer擂啥。不過netty并沒有直接使用nio的ByteBuffer哄陶,這主要是由于jdk的Buffer有以下幾個(gè)缺點(diǎn):
- 當(dāng)調(diào)用allocate方法分配內(nèi)存時(shí),Buffer的長度就固定了哺壶,不能動(dòng)態(tài)擴(kuò)展和收縮屋吨,當(dāng)寫入數(shù)據(jù)大于緩沖區(qū)的capacity時(shí)會(huì)發(fā)生數(shù)組越界錯(cuò)誤
- Buffer只有一個(gè)位置標(biāo)志位屬性position,讀寫切換時(shí)山宾,必須先調(diào)用flip或rewind方法至扰。不僅如此,因?yàn)閒lip的切換
- Buffer只提供了存取资锰、翻轉(zhuǎn)敢课、釋放、標(biāo)志绷杜、比較直秆、批量移動(dòng)等緩沖區(qū)的基本操作,想使用高級的功能(比如池化),就得自己手動(dòng)進(jìn)行封裝及維護(hù)鞭盟,使用非常不方便圾结。
也因此,netty實(shí)現(xiàn)了自己的緩沖區(qū)——ByteBuf,連名字都如此相似齿诉。那么ByteBuf是如何規(guī)避ByteBuffer的缺點(diǎn)的筝野?
第一點(diǎn)顯然是很好解決的,由于ByteBuf底層也是數(shù)組,那么它就可以像ArrayList一樣鹃两,在寫入操作時(shí)進(jìn)行容量檢查遗座,當(dāng)容量不足時(shí)進(jìn)行擴(kuò)容。
第二點(diǎn)俊扳,ByteBuf通過2個(gè)索引readerIndex,writerIndex將數(shù)組分為3部分猛遍,如下圖所示
+-------------------+------------------+------------------+
| discardable bytes | readable bytes | writable bytes |
| | (CONTENT) | |
+-------------------+------------------+------------------+
| | | |
0 <= readerIndex <= writerIndex <= capacity
初始化時(shí)馋记,readerIndex和writerIndex都是0号坡,隨著數(shù)據(jù)的寫入writerIndex會(huì)增加,此時(shí)readable byte部分增加梯醒,writable bytes減少宽堆。當(dāng)讀取時(shí),discardable bytes增加茸习,readable bytes減少畜隶。由于讀操作只修改readerIndex,寫操作只修改writerIndex号胚,讓ByteBuf的使用更加容易理解籽慢,避免了由于遺漏flip導(dǎo)致的功能異常。
此外猫胁,當(dāng)調(diào)用discardReadBytes方法時(shí)箱亿,可以把discardable bytes這部分的內(nèi)存釋放∑眩總體想法是通過將readerIndex移動(dòng)到0届惋,writerIndex移動(dòng)到writerIndex-readerIndex下標(biāo),具體移動(dòng)下標(biāo)的方式依據(jù)ByteBuf實(shí)現(xiàn)類有所不同菠赚。這個(gè)方法可以顯著提高緩沖區(qū)的空間復(fù)用率脑豹,避免無限度的擴(kuò)容,但會(huì)發(fā)生字節(jié)數(shù)組的內(nèi)存復(fù)制衡查,屬于以時(shí)間換空間的做法晨缴。
ByteBuf重要API
read、write峡捡、set击碗、skipBytes
前3個(gè)系列的方法及最后一個(gè)skipBytes都屬于改變指針的方法。舉例來說们拙,readByte會(huì)移動(dòng)readerIndex1個(gè)下標(biāo)位稍途,而int是4個(gè)byte的大小,所以readInt會(huì)移動(dòng)readerIndex4個(gè)下標(biāo)位砚婆,相應(yīng)的械拍,writeByte會(huì)移動(dòng)writerIndex1個(gè)下標(biāo)位,writeInt會(huì)移動(dòng)writerIndex4個(gè)下標(biāo)位装盯。set系列方法比較特殊坷虑,它的參數(shù)為index和value,意即將value寫入指定的index位置埂奈,但這個(gè)操作不會(huì)改變r(jià)eaderIndex和writerIndex迄损。skipBytes比較簡單粗暴,直接將readerIndex移動(dòng)指定長度账磺。
mark和reset
markReaderIndex和markWriterIndex可以將對應(yīng)的指針做一個(gè)標(biāo)記芹敌,當(dāng)需要重新操作這部分?jǐn)?shù)據(jù)時(shí)痊远,再使用resetReaderIndex或resetWriterIndex凳寺,將對應(yīng)指針復(fù)位到mark的位置翻诉。
duplicate剔猿、slice羡玛、copy
這3種方法都可以復(fù)制一份字節(jié)數(shù)組魄眉,不同之處在于duplicate和slice兩個(gè)方法返回的新ByteBuf和原有的老ByteBuf之間的內(nèi)容會(huì)互相影響票彪,而copy則不會(huì)扫步。duplicate和slice的區(qū)別在于前者復(fù)制整個(gè)ByteBuf的字節(jié)數(shù)組蝙云,而后者默認(rèn)僅復(fù)制可讀部分捆等,但可以通過slice(index, length)分割指定的區(qū)間滞造。
retain、release
這是ByteBuf接口繼承自ReferenceCounted接口的方法楚里,用于引用計(jì)數(shù)断部,以便在不使用對象時(shí)及時(shí)釋放。實(shí)現(xiàn)思路是當(dāng)需要使用一個(gè)對象時(shí)班缎,計(jì)數(shù)加1蝴光;不再使用時(shí),計(jì)數(shù)減1达址∶锼睿考慮到多線程場景,一般也多采用AtomicInteger實(shí)現(xiàn)沉唠。netty卻另辟蹊徑疆虚,選擇了volatile + AtomicIntegerFieldUpdater這樣一種更節(jié)省內(nèi)存的方式。
ByteBuf擴(kuò)容
在ByteBuf寫入數(shù)據(jù)時(shí)會(huì)檢查可寫入的容量满葛,若容量不足會(huì)進(jìn)行擴(kuò)容径簿。
final void ensureWritable0(int minWritableBytes) {
if (minWritableBytes <= writableBytes()) {
return;
}
int minNewCapacity = writerIndex + minWritableBytes;
int newCapacity = alloc().calculateNewCapacity(minNewCapacity, maxCapacity);
int fastCapacity = writerIndex + maxFastWritableBytes();
if (newCapacity > fastCapacity && minNewCapacity <= fastCapacity) {
newCapacity = fastCapacity;
}
capacity(newCapacity);
}
忽略一些檢驗(yàn)性質(zhì)的代碼后,可以看到擴(kuò)容時(shí)先嘗試將現(xiàn)有寫索引加上需要寫入的容量大小作為最小新容量嘀韧,并調(diào)用ByteBufAllocate的calculateNewCapacity方法進(jìn)行計(jì)算篇亭。跟入這個(gè)方法:
public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {
final int threshold = CALCULATE_THRESHOLD; // 4 MiB page
if (minNewCapacity == threshold) {
return threshold;
}
if (minNewCapacity > threshold) {
int newCapacity = minNewCapacity / threshold * threshold;
if (newCapacity > maxCapacity - threshold) {
newCapacity = maxCapacity;
} else {
newCapacity += threshold;
}
return newCapacity;
}
int newCapacity = 64;
while (newCapacity < minNewCapacity) {
newCapacity <<= 1;
}
return Math.min(newCapacity, maxCapacity);
}
可以看到這個(gè)方法的目的則是計(jì)算比可寫容量稍大的2的冪次方。minNewCapacity由上一個(gè)方法傳入锄贷,而maxCapacity則為Integer.MAX_VALUE译蒂。具體步驟是首先判斷新容量minNewCapacity是否超過了計(jì)算限制CALCULATE_THRESHOLD,默認(rèn)為4M谊却,如果沒有超過4MB柔昼,那么從64B開始不斷以2的冪次方形式擴(kuò)容,直到newCapacity超過minNewCapacity炎辨。而若一開始新容量就超過了4M捕透,則調(diào)整新容量到4M的倍數(shù)+1。比如newCapacity為6M,因?yàn)?/4 = 1,所以調(diào)整為(1+1)*4M=8M激率。
在計(jì)算完容量之后會(huì)調(diào)用capacity方法咳燕。這是一個(gè)抽象方法勿决,這里以UnpooledHeapByteBuf為例乒躺。
public ByteBuf capacity(int newCapacity) {
checkNewCapacity(newCapacity);
byte[] oldArray = array;
int oldCapacity = oldArray.length;
if (newCapacity == oldCapacity) {
return this;
}
int bytesToCopy;
if (newCapacity > oldCapacity) {
bytesToCopy = oldCapacity;
} else {
trimIndicesToCapacity(newCapacity);
bytesToCopy = newCapacity;
}
byte[] newArray = allocateArray(newCapacity);
System.arraycopy(oldArray, 0, newArray, 0, bytesToCopy);
setArray(newArray);
freeArray(oldArray);
return this;
}
首先檢查newCapacity是否大于0且小于最大容量。之后準(zhǔn)備好老數(shù)組要復(fù)制的長度低缩。trimIndicesToCapacity(newCapacity)是縮容時(shí)調(diào)用的嘉冒,它將readerIndex和newCapacity的較小值設(shè)置為新的readerIndex,將newCapacity設(shè)置為新的writerIndex咆繁。
之后便分配一個(gè)新數(shù)組讳推,并開始復(fù)制舊數(shù)組的元素。復(fù)制成功后玩般,將新數(shù)組保存為成員變量银觅,將老數(shù)組釋放掉。
ByteBuf種類
出于性能和空間的多方考慮坏为,netty從3個(gè)維度定義了各種不同的ByteBuf實(shí)現(xiàn)類究驴,主要是池化、堆內(nèi)堆外匀伏、可否使用Unsafe類這3個(gè)維度洒忧,從而演化出8種不同的ByteBuf,它們分別是PooledUnsafeHeapBytebuf够颠、PooledHeapByteBuf熙侍、PooledUnsafeDirectByteBuf、PooledDirectBytebuf履磨、UnpooledUnsafeHeapByteBuf蛉抓、UnpooledHeapByteBuf、UnpooledUnsafeDirectByteBuf剃诅、UnpooledDirectByteBuf巷送。
ByteBuf接口之下有一個(gè)抽象類AbstractByteBuf,實(shí)現(xiàn)了接口定義的read综苔、write惩系、set相關(guān)的方法,但在實(shí)現(xiàn)時(shí)只做了檢查如筛,而具體邏輯則定義一系列以_開頭的proteced方法堡牡,留待子類實(shí)現(xiàn)。
ByteBufAllocate
不同于一般形式的創(chuàng)建對象杨刨,ByteBuf需要通過內(nèi)存分配器ByteBufAllocate分配晤柄,對應(yīng)于不同的ByteBuf也會(huì)有不同的BtteBufferAllocate。netty將之抽象為ByteBufAllocate接口妖胀。我們看一下有哪些方法:
- buffer()芥颈、buffer(initialCapacity)惠勒、buffer(initialCapacity、maxCapacity)爬坑,分配ByteBuf的方法纠屋,具體分配的Buffer是堆內(nèi)還是堆外則由實(shí)現(xiàn)類決定。2個(gè)重載方法分別以給定初始容量盾计、最大容量的方式分配內(nèi)存
- ioBuffer()售担、ioBuffer(initialCapacity)、ioBuffer(initialCapacity署辉、maxCapacity)更傾向于分配堆外內(nèi)存的方法族铆,因?yàn)槎淹鈨?nèi)存更適合用于IO操作。重載方法同上
- heapBuffer()哭尝、heapBuffer(initialCapacity)哥攘、heapBuffer(initialCapacity、maxCapacity)分配堆內(nèi)內(nèi)存的方法材鹦。
- directBuffer()逝淹、directBuffer(initialCapacity)、directBuffer(initialCapacity侠姑、maxCapacity)分配堆外內(nèi)存的方法创橄。
- compositeBuffer()∶Ш欤可以將多個(gè)ByteBuf合并為一個(gè)ByteBuf妥畏,多個(gè)ByteBuf可以部分是堆內(nèi)內(nèi)存,部分是堆外內(nèi)存安吁。
ByteBufAllocate接口定義了heap和direct這一個(gè)維度醉蚁,其他維度則交由子類來定義。
UnPooledByteBufAllocate
ByteBufAllocate有一個(gè)直接實(shí)現(xiàn)類AbstractByteBufAllocate鬼店,它實(shí)現(xiàn)了大部分方法网棍,只留下2個(gè)抽象方法newHeapBuffer和newDirectBuffer交由子類實(shí)現(xiàn)。AbstractByteBufAllocate有2個(gè)子類PooledByteBufAllocate和UnpooledByteBufAllocate妇智,在這里定義了pooled池化維度的分配方式滥玷。
看看UnpooledByteBufAllocate如何實(shí)現(xiàn)2個(gè)抽象方法:
newHeapBuffer
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
return PlatformDependent.hasUnsafe() ?
new UnpooledUnsafeHeapByteBuf(this, initialCapacity, maxCapacity) :
new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
}
可以看到實(shí)現(xiàn)類根據(jù)PlatformDependent.hasUnsafe()方法自動(dòng)判定是否使用unsafe維度,這個(gè)方法通過在靜態(tài)代碼塊中嘗試初始化sun.misc.Unsafe來判斷Unsafe類是否在當(dāng)前平臺(tái)可用巍棱,在juc中惑畴,這個(gè)類使用頗多,作為與高并發(fā)打交道的netty航徙,出現(xiàn)這個(gè)類不令人意外如贷。UnpooledUnsafeHeapByteBuf與UnpooledHeapByteBuf并不是平級關(guān)系,事實(shí)上前者繼承了后者,在構(gòu)造方法上也直接調(diào)用UnpooledHeapByteBuf的構(gòu)造方法杠袱。構(gòu)造方法比較簡單尚猿,初始化byte數(shù)組、初始容量楣富、最大容量凿掂,將讀寫指針的設(shè)置為0,并將子類傳入的this指針保存到alloc變量中菩彬。
兩種Bytebuf的區(qū)別在于unsafe會(huì)嘗試通過反射的方式創(chuàng)建byte數(shù)組缠劝,并將數(shù)組的地址保存起來潮梯,之后再獲取數(shù)據(jù)時(shí)也會(huì)調(diào)用Unsafe的getByte方法骗灶,通過數(shù)組在內(nèi)存中的地址+偏移量的形式直接獲取,而普通的SafeByteBuf則是保存byte數(shù)組秉馏,通過數(shù)組索引即array[index]訪問耙旦。
// UnsafeHeapByteBuf初始化數(shù)組
protected byte[] allocateArray(int initialCapacity) {
return PlatformDependent.allocateUninitializedArray(initialCapacity);
}
// HeapByteBuf初始化數(shù)組
protected byte[] allocateArray(int initialCapacity) {
return new byte[initialCapacity];
}
// UnsafeHeapByteBuf通過UnsafeByteBufUtil獲取字節(jié)
static byte getByte(byte[] data, int index) {
return UNSAFE.getByte(data, BYTE_ARRAY_BASE_OFFSET + index);
}
// HeapByteBuf獲取字節(jié)
static byte getByte(byte[] memory, int index) {
return memory[index];
}
newDirectBuffer
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
return PlatformDependent.hasUnsafe() ?
new UnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity) :
new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
}
DirectByteBuf構(gòu)造方法大致與heap的類似,只是保存數(shù)據(jù)的容器由字節(jié)數(shù)組變?yōu)榱薺dk的ByteBuffer萝究。相應(yīng)的免都,分配與釋放內(nèi)存的方法也變成調(diào)用jdk的ByteBuffer方法。而UnsafeByteBuf更是直接用long類型記錄內(nèi)存地址帆竹。
// DirectByteBuf獲取字節(jié)
protected byte _getByte(int index) {
return buffer.get(index);
}
// UnsafeDirectByteBuf獲取字節(jié)
protected byte _getByte(int index) {
return UnsafeByteBufUtil.getByte(addr(index));
}
// 獲取內(nèi)存地址
final long addr(int index) {
return memoryAddress + index;
}
// UnsafeByteBufUtil獲取字節(jié)
static byte getByte(long address) {
return UNSAFE.getByte(address);
}
由于PooledByteBufAllocate內(nèi)容較為龐大绕娘,放入下一節(jié)講述。
未完待續(xù)···