什么是ByteBuf
Netty提供了強(qiáng)大的隨機(jī)
和順序訪問(wèn)零字節(jié)或多個(gè)字節(jié)的序列
,為
一個(gè)或多個(gè)原始的字節(jié)數(shù)組
和JDK中NIO包下的ByteBuffer
提供了抽像視圖
通俗的理解就是一個(gè)byte數(shù)組的緩沖區(qū)
如何創(chuàng)建
推薦
創(chuàng)建buffer的方式
是使用Pooled
或Unpooled
類靜態(tài)方法,而不是通過(guò)調(diào)用構(gòu)造器的方式
這里以Unpooled類為例:
- 創(chuàng)建一個(gè)
初始容量為256
,最大容量
為Integer.MAX_VALUE
的字節(jié)緩沖區(qū)
Unpooled.buffer();
- 創(chuàng)建一個(gè)
自定義初始容量大小
,最大容量
為Integer.MAX_VALUE
的字節(jié)緩沖區(qū)
Unpooled.buffer(int initialCapacity);
- 創(chuàng)建一個(gè)
初始容量為256
,最大容量
為Integer.MAX_VALUE
的直接字節(jié)緩沖區(qū)
Unpooled.directBuffer()
- 創(chuàng)建一個(gè)
自定義初始容量大小
,最大容量
為Integer.MAX_VALUE
的直接字節(jié)緩沖區(qū)
Unpooled.directBuffer(int initialCapacity)
如何使用
1. 隨機(jī)訪問(wèn)
ByteBuf使用了從0開(kāi)始的索引,就像普通的原始字節(jié)數(shù)組一樣,這意味著第一個(gè)字節(jié)的索引總是0,而最后一個(gè)字節(jié)的索引總是capacity - 1
例如,迭代所有緩沖區(qū)中的字節(jié),可以使用類似如下的代碼
ByteBuf buffer = ...;
for (int i = 0; i < buffer.capacity(); i ++){
byte b = buffer.getByte(i);
System.out.println((char) b);
}
2. 順序訪問(wèn)
ByteBuf 提供了
兩個(gè)指針變量
來(lái)支撐
順序讀和寫操作,即readerIndex
和writeIndex
我們用一張圖來(lái)展示一個(gè)緩沖區(qū)
是如何通過(guò)這兩個(gè)指針變量被分割成三個(gè)區(qū)域
的
3. 可讀(Readable)字節(jié)
可讀字節(jié)又稱
真實(shí)字節(jié)內(nèi)容
,表示的是實(shí)際
被存儲(chǔ)在緩沖區(qū)
中的數(shù)據(jù),任何
以read或skip
開(kāi)頭的操作都將會(huì)獲得
和跳過(guò)
從當(dāng)前readerIndex索引的數(shù)據(jù)
,然后根據(jù)
讀取字節(jié)的數(shù)量
來(lái)增加
這個(gè)索引值
如果讀操作的參數(shù)
也是一個(gè)ByteBuf
,并且沒(méi)指定
目標(biāo)索引,那么該參數(shù)buffer的writerIndex值將亦隨之增加
,意思就是調(diào)用read方法的buffer字節(jié)數(shù)組緩沖區(qū)中的字節(jié)數(shù)據(jù)會(huì)被讀出來(lái)再寫入進(jìn)參數(shù)buffer中,所以參數(shù)buffer緩沖區(qū)的writerIndex值會(huì)增加
需要注意的是,一個(gè)新分配
,包裝
或復(fù)制
的緩沖區(qū)的readerIndex
值是0
迭代緩沖區(qū)的可讀字節(jié)代碼示例:
while (buffer.isReadable()) {
System.out.println(buffer.readByte());
}
4. 可寫(Writable)字節(jié)
這是一個(gè)
需要
被填充
的未定義的空間
,任何以write開(kāi)頭命名的操作都將從當(dāng)前的writeIndex寫入數(shù)據(jù),并根據(jù)被寫入數(shù)據(jù)的字節(jié)數(shù)量來(lái)增加該writeIndex的值
如果寫操作的參數(shù)
也是一個(gè)ByteBuf
,并且沒(méi)指定
源索引,那么該參數(shù)buffer的readerIndex值將亦隨之增加
,意思就是參數(shù)buffer中的字節(jié)數(shù)據(jù)會(huì)被讀出來(lái)再寫入進(jìn)調(diào)用write方法的buffer中
此外值得注意的是,一個(gè)新分配(allocated
)buffer的默認(rèn)writeIndex值
為0
,而包裝或拷貝(wrapped/copied
)的緩沖區(qū)的默認(rèn)writeIndex值
為該buffer緩沖區(qū)的容量的大小即capacity
下面是一個(gè)用隨機(jī)整數(shù)填充buffer可寫字節(jié)的代碼示例:
ByteBuf buffer = ...;
// maxWritableBytes方法是返回該buffer最大可寫字節(jié)的數(shù)量
// 因?yàn)閕nt類型在Java中占用4個(gè)字節(jié),故這里>=4即代表可以寫一
// 個(gè)int值進(jìn)buffer中
while (buffer.maxWritableBytes() >= 4) {
buffer.writeInt(random.nextInt());
}
這里簡(jiǎn)單提一下buffer.maxWritableBytes()
方法的判斷依據(jù),其實(shí)就是根據(jù)buffer的maxCapacity - writeIndex
的值
5. 可廢棄(Discardable)字節(jié)
所謂可廢棄字節(jié)就是指那些
已經(jīng)被
讀操作讀過(guò)的字節(jié)
,最初的時(shí)候,可廢棄字節(jié)段的大小是0。但是當(dāng)讀操作被執(zhí)行時(shí),可廢棄字節(jié)段的大小會(huì)隨著讀操作一直增加到writeIndex
需要注意的是我們可以通過(guò)調(diào)用discardReadByted()方法來(lái)丟棄0索引
至readerIndex索引
之間的字節(jié),我們可以通過(guò)上面畫出的buffer圖來(lái)理解
6. 清除(Clear)buffer緩沖區(qū)索引
通過(guò)調(diào)用clear()方法
,可以同時(shí)設(shè)置readerIndex和writeIndex值為0,但是這僅僅只是設(shè)置這兩個(gè)指針的值,并不是真的清除緩沖區(qū)的數(shù)據(jù)內(nèi)容
7. 搜索(For Search)操作
對(duì)于簡(jiǎn)單的單字節(jié)搜索,使用indexOf(...) 和 bytesBefore(...)是尤其有用的
對(duì)于復(fù)雜搜索而言,需要使用forEachByte(...)函數(shù)完成,該函數(shù)有一個(gè)ByteProcessor類型的字節(jié)處理器參數(shù)
8. 標(biāo)記和重置(Mark and reset)
每一個(gè)buffer都有兩個(gè)標(biāo)記索引
,一個(gè)用來(lái)存儲(chǔ)readerIndex
,一個(gè)用來(lái)存儲(chǔ)writeIndex
我們總是可以通過(guò)調(diào)用一個(gè)重置方法(reset)來(lái)重新定位這兩個(gè)索引,他們的工作方式和InputStream的mark和reset方法很相似,除了沒(méi)有讀限制而已
9. 派生緩沖區(qū)(Derived buffers)
我們可以通過(guò)以下方法創(chuàng)建一個(gè)已存在緩沖區(qū)的視圖,派生方法分非保留
以及保留
兩類
- 非保留派生
duplicate()
slice()以及slice(int, int)
readSlice()
- 保留派生
retainedDuplicate()
retainedSlice()以及retainedSlice(int, int)
readRetainedSlice(int)
注意以上方法只是返回一個(gè)已存在緩沖區(qū)的視圖
而已,所謂視圖就是一種新的展示形式
,一個(gè)派生的緩沖區(qū)將會(huì)有一個(gè)獨(dú)立的readerIndex
,writerIndex
以及標(biāo)記索引(marker indexs)
,同時(shí)會(huì)共享
其他內(nèi)部數(shù)據(jù)
表示
如果需要一個(gè)對(duì)于已存在緩沖區(qū)的完全拷貝
,需要調(diào)用copy()
方法
非保留和保留的區(qū)別
非保留的派生方法不會(huì)再返回的派生緩沖區(qū)的基礎(chǔ)上調(diào)用retain()方法,因此它的引用計(jì)數(shù)不會(huì)增加
如果你需要去創(chuàng)建一個(gè)增加引用計(jì)數(shù)
的派生緩沖區(qū),就考慮使用retained開(kāi)頭的保留派生
方法,如retainedDuplicate()等,保留方式派生的緩沖區(qū)會(huì)帶來(lái)更少的垃圾回收
10. 和現(xiàn)有JDK類型的轉(zhuǎn)換
實(shí)際開(kāi)發(fā)中,經(jīng)常會(huì)涉及到將ByteBuf和已有JDK類型之間的轉(zhuǎn)換
- NIO Buffers之間的轉(zhuǎn)換
可以通過(guò)nioBuffer()
方法實(shí)現(xiàn)與NIO ByteBuffer之間的轉(zhuǎn)換,但是需要注意的是返回的ByteBuffer
將會(huì)共享
或包含
該buffer拷貝
的數(shù)據(jù),即當(dāng)我們改變
返回的NIO ByteBuffer
的指針和limit的時(shí)候,并不會(huì)影響
到原來(lái)的ByteBuf的索引和標(biāo)記
- Strings之間轉(zhuǎn)換
通過(guò)各種各樣的toString(Chartset)
方法來(lái)將一個(gè)ButeBuf轉(zhuǎn)換為一個(gè)String,注意不是Object類型的toString() 方法哦
- I/O Streams之間的轉(zhuǎn)換
通過(guò)ByteBufOutputStream
和ByteBufInputStream
實(shí)現(xiàn)
如何分類
既然是一個(gè)緩沖區(qū),那么就會(huì)涉及到兩點(diǎn),內(nèi)存
分配/回收效率
以及如何優(yōu)化內(nèi)存資源使用
下面分別基于這兩個(gè)視角來(lái)分類ByteBuf
分配/回收效率角度
從內(nèi)存分配的角度看,ByteBuf可以分為兩類
- 堆內(nèi)
堆內(nèi)分配的字節(jié)緩沖區(qū)的優(yōu)點(diǎn)是內(nèi)存分配
和回收速度快
,因?yàn)槠?code>基于JVM的分配和回收,不
需要發(fā)生
系統(tǒng)調(diào)用
而缺點(diǎn)
方面,如果是進(jìn)行網(wǎng)絡(luò)IO操作,會(huì)存在二次內(nèi)存復(fù)制
(JVM會(huì)先將堆內(nèi)緩沖區(qū)先copy
到堆外直接內(nèi)存,再?gòu)亩淹庵苯觾?nèi)存copy
數(shù)據(jù)到內(nèi)核的Socket緩沖區(qū)),性能
方面會(huì)有所下降
- 堆外
堆外內(nèi)存又稱直接內(nèi)存,其直接受操作系統(tǒng)管理(而不是JVM)
Java堆外內(nèi)存分配的字節(jié)緩沖區(qū)對(duì)象,會(huì)先通過(guò)Unsafe類
的接口直接調(diào)用os::malloc
來(lái)分配內(nèi)存,然后返回
分配到的內(nèi)存的起始地址
和大小
,并利用java.nio.DirectByteBuffer
對(duì)象保存這些值
這樣就可以利用DirectByteBuffer對(duì)象直接操作直接內(nèi)存,這些內(nèi)存只有在DirectByteBuffer對(duì)象
被回收掉之后才有機(jī)會(huì)被os回收,因此如果這些對(duì)象大部分
都移到了old區(qū)
,但是一直沒(méi)有觸發(fā)CMS GC
或Full GC
,那么悲劇將會(huì)發(fā)生,因?yàn)槟愕?code>物理內(nèi)存被他們耗盡
了,因此為了避免這種悲劇的發(fā)生,通過(guò)-XX:MaxDirectMemorySize
來(lái)指定最大的堆外內(nèi)存大小
,當(dāng)使用達(dá)到了閾值的時(shí)候?qū)⒄{(diào)用System.gc來(lái)做一次full gc,以此來(lái)回收掉被使用的堆外內(nèi)存
那使用直接內(nèi)存的好處就是對(duì)于網(wǎng)絡(luò)IO
,節(jié)省
了一次內(nèi)存復(fù)制
操作,無(wú)需將堆內(nèi)數(shù)據(jù)先copy到堆外(native堆),而是直接從native堆(堆外)將數(shù)據(jù)copy到網(wǎng)絡(luò)socket緩沖區(qū)發(fā)送即可
分類 | 優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|---|
堆內(nèi) | 分配和回收速度快,易于控制 | 對(duì)于網(wǎng)絡(luò)IO操作,存在二次內(nèi)存復(fù)制 |
堆外 | 對(duì)于網(wǎng)絡(luò)IO操作,少一次內(nèi)存復(fù)制,速度快 | 容易存在內(nèi)存泄露,難以控制 |
優(yōu)化內(nèi)存資源使用角度
無(wú)論何時(shí),內(nèi)存都是寶貴的資源,試想如果我們不限制的創(chuàng)建(分配內(nèi)存空間)字節(jié)緩沖區(qū)ByteBuf的話,將是多么的浪費(fèi)資源
因此有了池化
和非池化
的區(qū)別,這里可以類比線程池的設(shè)計(jì)
思路
- 基于對(duì)象池(
Pooled
)的ByteBuf
優(yōu)點(diǎn)是可以重用ByteBuf對(duì)象
,提升內(nèi)存使用效率
的同時(shí)又避免
了頻繁的分配和回收內(nèi)存
開(kāi)銷
缺點(diǎn)則是使用內(nèi)存池會(huì)增加管理和維護(hù)的復(fù)雜性
- 基于非對(duì)象池(
Unpooled
)的ByteBuf
優(yōu)點(diǎn)是使用簡(jiǎn)單
,維護(hù)簡(jiǎn)單,缺點(diǎn)則是GC
或操作系統(tǒng)
負(fù)擔(dān)重
分類 | 優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|---|
Pooled 池化 | 重用ByteBuf對(duì)象,提升內(nèi)存使用效率且避免了頻繁的分配和回收內(nèi)存開(kāi)銷 | 增加管理和維護(hù)的復(fù)雜性 |
Unpooled 非池化 | 使用簡(jiǎn)單 | GC負(fù)擔(dān)重 |
OK,ByteBuf的基本使用介紹完以后,下面我們將基于源碼對(duì)ButeBuf的實(shí)現(xiàn)原理進(jìn)行深入的分析
源碼
基于ByteBuf的創(chuàng)建流程來(lái)一步一步學(xué)習(xí)ByteBuf相關(guān)源碼,為了降低學(xué)習(xí)難度,我們先從
Unpooled類
入手
一個(gè)字節(jié)緩沖區(qū)的創(chuàng)建,本質(zhì)上就是申請(qǐng)一塊內(nèi)存空間
一個(gè)
非池化
創(chuàng)建ButeBuf的工具類
通過(guò)分配新的內(nèi)存空間
或通過(guò)包裝/拷貝
一個(gè)已存在
的字節(jié)數(shù)組
,字節(jié)緩沖區(qū)
和一個(gè)String
來(lái)創(chuàng)建一個(gè)新的ByteBuf對(duì)象
該類被final
關(guān)鍵字修飾,并提供了大量的靜態(tài)方法
來(lái)實(shí)現(xiàn)創(chuàng)建ByteBuf對(duì)象,其ByteBuf對(duì)象創(chuàng)建工作實(shí)際上是委托給了內(nèi)置默認(rèn)的非池化字節(jié)分配器
public final class Unpooled {
// 默認(rèn)的非池化字節(jié)分配器
private static final ByteBufAllocator ALLOC = UnpooledByteBufAllocator.DEFAULT;
創(chuàng)建堆內(nèi)以及堆外字節(jié)緩沖區(qū)
/**
* Creates a new big-endian Java heap buffer with reasonably small initial capacity, which
* expands its capacity boundlessly on demand.
*/
public static ByteBuf buffer() {
return ALLOC.heapBuffer();
}
/**
* Creates a new big-endian direct buffer with reasonably small initial capacity, which
* expands its capacity boundlessly on demand.
*/
public static ByteBuf directBuffer() {
return ALLOC.directBuffer();
}
一個(gè)
非池化字節(jié)
緩沖分配器
- UML
接著看調(diào)用鏈,發(fā)現(xiàn)heapBuffer方法是其抽象父類AbstractByteBufAllocator
實(shí)現(xiàn)的
該分配器抽象類實(shí)現(xiàn)了接口ByteBufAllocator中的大部分方法,故UnpooledByteBufAllocator的方法調(diào)用大部分都是調(diào)用了其抽象基類的,這里我們重點(diǎn)看下這個(gè)抽象分配器類
public abstract class AbstractByteBufAllocator implements ByteBufAllocator {
// buffer默認(rèn)初始容量
static final int DEFAULT_INITIAL_CAPACITY = 256;
// 默認(rèn)最大容量
static final int DEFAULT_MAX_CAPACITY = Integer.MAX_VALUE;
// 是否默認(rèn)直接內(nèi)存分配
private final boolean directByDefault;
// 構(gòu)造函數(shù)
protected AbstractByteBufAllocator(boolean preferDirect) {
// 根據(jù)是否傾向直接內(nèi)存以及
// 是否在class path下發(fā)現(xiàn)有可以加速直接內(nèi)存訪問(wèn)的sun.misc.Unsafe類
directByDefault = preferDirect && PlatformDependent.hasUnsafe();
emptyBuf = new EmptyByteBuf(this);
}
@Override
public ByteBuf heapBuffer() {
return heapBuffer(DEFAULT_INITIAL_CAPACITY, DEFAULT_MAX_CAPACITY);
}
可以看到這里調(diào)用了重載參數(shù)的heapBuffer方法,并傳入了默認(rèn)初始buffer容量以及默認(rèn)最大容量
@Override
public ByteBuf heapBuffer(int initialCapacity, int maxCapacity) {
// 1. 如果初始容量以及最大容量都為0,則返回一個(gè)空的buffer對(duì)象
if (initialCapacity == 0 && maxCapacity == 0) {
return emptyBuf;
}
// 2. 校驗(yàn)參數(shù)合法性,主要是initialCapacity不可以大于maxCapacity,否則會(huì)拋IllegalArgumentException異常
validate(initialCapacity, maxCapacity);
// 3. 主要的創(chuàng)建方法
return newHeapBuffer(initialCapacity, maxCapacity);
}
/**
* Create a heap {@link ByteBuf} with the given initialCapacity and maxCapacity.
*/
protected abstract ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity);
這里看到newHeapBuffer方法是一個(gè)protected修飾
的抽象
方法,其具體的實(shí)現(xiàn)由子類
完成,我們將視角轉(zhuǎn)向子類UnpooledByteBufAllocator
@Override
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
return PlatformDependent.hasUnsafe() ?
new InstrumentedUnpooledUnsafeHeapByteBuf(this, initialCapacity, maxCapacity) :
new InstrumentedUnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
}
這里會(huì)根據(jù)PlatformDependent.hasUnsafe()方法返回值分別調(diào)用不同ByteBuf實(shí)現(xiàn)類的構(gòu)造函數(shù)
hasUnsafe內(nèi)部就是去判斷在class path下是否有可以加速直接內(nèi)存訪問(wèn)的sun.misc.Unsafe類
我們以InstrumentedUnpooledUnsafeHeapByteBuf
內(nèi)部類為例
private static final class InstrumentedUnpooledUnsafeHeapByteBuf extends UnpooledUnsafeHeapByteBuf {
InstrumentedUnpooledUnsafeHeapByteBuf(UnpooledByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
super(alloc, initialCapacity, maxCapacity);
}
@Override
protected byte[] allocateArray(int initialCapacity) {
byte[] bytes = super.allocateArray(initialCapacity);
((UnpooledByteBufAllocator) alloc()).incrementHeap(bytes.length);
return bytes;
}
@Override
protected void freeArray(byte[] array) {
int length = array.length;
super.freeArray(array);
((UnpooledByteBufAllocator) alloc()).decrementHeap(length);
}
}
- UML類圖
可以看到其構(gòu)造函數(shù)就是調(diào)用父類的構(gòu)造,且傳入了this參數(shù),作為即將分配字節(jié)緩沖區(qū)的分配器引用,我們逐一查看
大字節(jié)序的Java堆字節(jié)緩沖區(qū)實(shí)現(xiàn)類
public class UnpooledHeapByteBuf extends AbstractReferenceCountedByteBuf {
// 持有所屬分配器引用
private final ByteBufAllocator alloc;
// 實(shí)際存儲(chǔ)字節(jié)數(shù)組
byte[] array;
// 臨時(shí)NIO buffer
private ByteBuffer tmpNioBuf;
// 構(gòu)造函數(shù)為public,表示也可以直接調(diào)用,一般不推薦
public UnpooledHeapByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
// 繼續(xù)調(diào)用父類,初始化buffer的最大容量屬性
super(maxCapacity);
// 所屬分配器不可為null
checkNotNull(alloc, "alloc");
// 校驗(yàn)容量參數(shù)合法性
if (initialCapacity > maxCapacity) {
throw new IllegalArgumentException(String.format(
"initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
}
// 賦值所屬分配器實(shí)例引用
this.alloc = alloc;
// 1. 設(shè)置字節(jié)數(shù)組
setArray(allocateArray(initialCapacity));
// 2. 設(shè)置readerIndex,writerIndex索引
setIndex(0, 0);
}
// 分配數(shù)組方法
protected byte[] allocateArray(int initialCapacity) {
return new byte[initialCapacity];
}
這里可以看到allocateArray
方法被protected修飾,且子類InstrumentedUnpooledUnsafeHeapByteBuf重寫
了該方法,也就是說(shuō)這里實(shí)際上是調(diào)用了子類重寫后的方法,這里將視角轉(zhuǎn)到InstrumentedUnpooledUnsafeHeapByteBuf類
@Override
protected byte[] allocateArray(int initialCapacity) {
// 1. 調(diào)用父類UnpooledUnsafeHeapByteBuf分配字節(jié)數(shù)組方法,返回已分配的堆內(nèi)字節(jié)數(shù)組
byte[] bytes = super.allocateArray(initialCapacity);
// 2. 獲取所屬的分配器,并調(diào)用分配器的incrementHeap方法
((UnpooledByteBufAllocator) alloc()).incrementHeap(bytes.length);
return bytes;
}
- 調(diào)用父類
UnpooledUnsafeHeapByteBuf
類的分配字節(jié)數(shù)組方法
@Override
protected byte[] allocateArray(int initialCapacity) {
return PlatformDependent.allocateUninitializedArray(initialCapacity);
}
可以看到這里借助了PlatformDependent類,分配了一個(gè)未初始化的字節(jié)數(shù)組
static {
...
int tryAllocateUninitializedArray =
SystemPropertyUtil.getInt("io.netty.uninitializedArrayAllocationThreshold", 1024);
UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD = javaVersion() >= 9 && PlatformDependent0.hasAllocateArrayMethod() ?
tryAllocateUninitializedArray : -1;
}
public static byte[] allocateUninitializedArray(int size) {
return UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD < 0 || UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD > size ?
new byte[size] : PlatformDependent0.allocateUninitializedArray(size);
}
這里實(shí)際上滿足了三目表達(dá)式條件,直接new了一個(gè)byte數(shù)組返回
- 這里主要看下所屬分配器的incrementHeap方法做了什么,視角轉(zhuǎn)到分配器UnpooledByteBufAllocator
void incrementHeap(int amount) {
metric.heapCounter.add(amount);
}
這里是做了一個(gè)監(jiān)控,一個(gè)計(jì)數(shù)器去計(jì)數(shù)堆內(nèi)存已經(jīng)被創(chuàng)建的ByteBuf占了多少
方法調(diào)用結(jié)束,再將視角轉(zhuǎn)回到UnpooledHeapByteBuf類
的構(gòu)造函數(shù)
中的setArray處
private void setArray(byte[] initialArray) {
array = initialArray;
tmpNioBuf = null;
}
setArray
方法就是對(duì)實(shí)例屬性array進(jìn)行賦值以及將tmpNioBuf置為null
而setIndex方法
則調(diào)用了抽象父類AbstractByteBuf
的方法,我們繼續(xù)將視角轉(zhuǎn)向AbstractByteBuf類
public abstract class AbstractByteBuf extends ByteBuf {
// 讀索引
int readerIndex;
// 寫索引
int writerIndex;
// 已標(biāo)記讀索引
private int markedReaderIndex;
// 已標(biāo)記寫索引
private int markedWriterIndex;
// 最大容量
private int maxCapacity;
private static void checkIndexBounds(final int readerIndex, final int writerIndex, final int capacity) {
if (readerIndex < 0 || readerIndex > writerIndex || writerIndex > capacity) {
throw new IndexOutOfBoundsException(String.format(
"readerIndex: %d, writerIndex: %d (expected: 0 <= readerIndex <= writerIndex <= capacity(%d))",
readerIndex, writerIndex, capacity));
}
}
@Override
public ByteBuf setIndex(int readerIndex, int writerIndex) {
// 范圍校驗(yàn)
if (checkBounds) {
checkIndexBounds(readerIndex, writerIndex, capacity());
}
setIndex0(readerIndex, writerIndex);
return this;
}
final void setIndex0(int readerIndex, int writerIndex) {
this.readerIndex = readerIndex;
this.writerIndex = writerIndex;
}
至此Unpooled.buffer()方法的源碼已經(jīng)分析完畢,放一張堆內(nèi)分配非池化
字節(jié)緩沖區(qū)創(chuàng)建圖
下面我們分析一下采用非池化直接內(nèi)存
分配的字節(jié)緩沖區(qū)的創(chuàng)建流程
public static ByteBuf directBuffer() {
return ALLOC.directBuffer();
}
視角轉(zhuǎn)到非池化字節(jié)緩沖分配器(UnpooledByteBufAllocator
)的directBuffer
方法
同理,directBuffer
方法由其分配器的基類實(shí)現(xiàn),故視角轉(zhuǎn)到AbstractByteBufAllocator
抽象字節(jié)緩沖分配器類的directBuffer方法
@Override
public ByteBuf directBuffer() {
return directBuffer(DEFAULT_INITIAL_CAPACITY, DEFAULT_MAX_CAPACITY);
}
也是一個(gè)重載默認(rèn)參數(shù)的方法,繼續(xù)
@Override
public ByteBuf directBuffer(int initialCapacity, int maxCapacity) {
if (initialCapacity == 0 && maxCapacity == 0) {
return emptyBuf;
}
validate(initialCapacity, maxCapacity);
return newDirectBuffer(initialCapacity, maxCapacity);
}
若initialCapacity和maxCapacity均為0,則返回一個(gè)空的buffer
校驗(yàn)參數(shù)合法性
轉(zhuǎn)到newDirectBuffer方法創(chuàng)建直接內(nèi)存緩沖區(qū)實(shí)例
protected abstract ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity);
這里是一個(gè)抽象的protected修飾的
方法,具體的實(shí)現(xiàn)由子類UnpooledByteBufAllocator
實(shí)現(xiàn),我們跳轉(zhuǎn)一下到子類中去看
@Override
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
final ByteBuf buf;
// 1.
if (PlatformDependent.hasUnsafe()) {
// 2.
buf = noCleaner ? new InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf(this, initialCapacity, maxCapacity) :
new InstrumentedUnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
} else {
buf = new InstrumentedUnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
}
// 3.
return disableLeakDetector ? buf : toLeakAwareBuffer(buf);
}
該方法我們分三點(diǎn)來(lái)分析
利用PlatformDependent.hasUnsafe()判斷是否
在classpath
下存在sun.misc.Unsafe
類關(guān)于noCleaner請(qǐng)看
莫那一魯?shù)?/code>博友的Netty 內(nèi)存回收之 noCleaner 策略文章,這里為true,繼續(xù)往下走,調(diào)用
InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf類
的構(gòu)造函數(shù)根據(jù)是否禁用泄露檢測(cè)
disableLeakDetector
,決定是否檢測(cè)泄露緩沖區(qū)
同樣是UnpooledByteBufAllocator非池化字節(jié)緩沖分配器的內(nèi)部靜態(tài)類
- UML
- 源碼
private static final class InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf
extends UnpooledUnsafeNoCleanerDirectByteBuf {
InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf(
UnpooledByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
super(alloc, initialCapacity, maxCapacity);
}
@Override
protected ByteBuffer allocateDirect(int initialCapacity) {
ByteBuffer buffer = super.allocateDirect(initialCapacity);
((UnpooledByteBufAllocator) alloc()).incrementDirect(buffer.capacity());
return buffer;
}
@Override
ByteBuffer reallocateDirect(ByteBuffer oldBuffer, int initialCapacity) {
int capacity = oldBuffer.capacity();
ByteBuffer buffer = super.reallocateDirect(oldBuffer, initialCapacity);
((UnpooledByteBufAllocator) alloc()).incrementDirect(buffer.capacity() - capacity);
return buffer;
}
@Override
protected void freeDirect(ByteBuffer buffer) {
int capacity = buffer.capacity();
super.freeDirect(buffer);
((UnpooledByteBufAllocator) alloc()).decrementDirect(capacity);
}
}
這里的構(gòu)造函數(shù)調(diào)用父類UnpooledUnsafeDirectByteBuf
的構(gòu)造方法
$\color{red}{UnpooledUnsafeDirectByteBuf}
public class UnpooledUnsafeDirectByteBuf extends AbstractReferenceCountedByteBuf {
// 分配器引用
private final ByteBufAllocator alloc;
// 臨時(shí)NioBuf
private ByteBuffer tmpNioBuf;
private int capacity;
private boolean doNotFree;
// JDK的ByteBuffer
ByteBuffer buffer;
long memoryAddress;
public UnpooledUnsafeDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
// 1.
super(maxCapacity);
if (alloc == null) {
throw new NullPointerException("alloc");
}
checkPositiveOrZero(initialCapacity, "initialCapacity");
checkPositiveOrZero(maxCapacity, "maxCapacity");
if (initialCapacity > maxCapacity) {
throw new IllegalArgumentException(String.format(
"initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
}
// 2.
this.alloc = alloc;
// 3.
setByteBuffer(allocateDirect(initialCapacity), false);
}
// protected 修飾的方法,由于子類重寫了該方法,所以此處調(diào)用給了子類的allocateDirect方法
protected ByteBuffer allocateDirect(int initialCapacity) {
return ByteBuffer.allocateDirect(initialCapacity);
}
這里分三步分析
繼續(xù)向上調(diào)用
父類AbstractByteBuf
的構(gòu)造函數(shù),賦值maxCapacity
屬性值賦值自身屬性分配器alloc
這一步我們拆分為兩步走,第一步先調(diào)用allocateDirect方法分配直接內(nèi)存,第二部將分配完成的直接內(nèi)存通過(guò)setByteBuffer賦值到JDK的ByteBuffer屬性上
- allocateDirect分配直接內(nèi)存
由于子類InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf
重寫了分配內(nèi)存方法,故此處實(shí)際調(diào)用的是子類的方法
@Override
protected ByteBuffer allocateDirect(int initialCapacity) {
// 1.
ByteBuffer buffer = super.allocateDirect(initialCapacity);
// 2.
((UnpooledByteBufAllocator) alloc()).incrementDirect(buffer.capacity());
return buffer;
}
-
中調(diào)用父類
UnpooledUnsafeNoCleanerDirectByteBuf
的方法分配直接內(nèi)存@Override
protected ByteBuffer allocateDirect(int initialCapacity) {
return PlatformDependent.allocateDirectNoCleaner(initialCapacity);
}
還是同樣的套路,利用了PlatformDependent類
完成實(shí)際分配內(nèi)存的工作,返回一個(gè)JDK的ByteBuffer對(duì)象
public static ByteBuffer allocateDirectNoCleaner(int capacity) {
assert USE_DIRECT_BUFFER_NO_CLEANER;
// 1. 增加直接內(nèi)存分配計(jì)數(shù)器
incrementMemoryCounter(capacity);
try {
// 2.
return PlatformDependent0.allocateDirectNoCleaner(capacity);
} catch (Throwable e) {
decrementMemoryCounter(capacity);
throwException(e);
return null;
}
}
static ByteBuffer allocateDirectNoCleaner(int capacity) {
// Calling malloc with capacity of 0 may return a null ptr or a memory address that can be used.
// Just use 1 to make it safe to use in all cases:
// See: http://pubs.opengroup.org/onlinepubs/009695399/functions/malloc.html
return newDirectBuffer(UNSAFE.allocateMemory(Math.max(1, capacity)), capacity);
}
static ByteBuffer newDirectBuffer(long address, int capacity) {
ObjectUtil.checkPositiveOrZero(capacity, "capacity");
try {
return (ByteBuffer) DIRECT_BUFFER_CONSTRUCTOR.newInstance(address, capacity);
} catch (Throwable cause) {
// Not expected to ever throw!
if (cause instanceof Error) {
throw (Error) cause;
}
throw new Error(cause);
}
}
UNSAFE.allocateMemory(Math.max(1, capacity))方法返回的是一個(gè)直接內(nèi)存的地址
,本質(zhì)上是一個(gè)long型整數(shù)
public native long allocateMemory(long var1);
而具體直接內(nèi)存的地址獲取則是由sun.misc包
下的Unsafe類
的native方法
分配的
到這里我們看到是利用了java.nio.DirectByteBuffer
類的構(gòu)造器利用反射創(chuàng)建了一個(gè)DirectByteBuffer對(duì)象,傳入了已經(jīng)分配好的直接內(nèi)存地址(long型整數(shù))以及初始容量大小(256)
分配直接內(nèi)存總結(jié)起來(lái)有三點(diǎn):
增加直接內(nèi)存計(jì)數(shù)器
利用Unsafe類分配一個(gè)直接內(nèi)存地址,Long整數(shù)
調(diào)用JDK nio包的DirectByteBuffer構(gòu)造函數(shù)反射創(chuàng)建對(duì)象返回
分配直接內(nèi)存方法結(jié)束后,將視角重新回到UnpooledUnsafeDirectByteBuf類
,看下setByteBuffer方法
做了什么
final void setByteBuffer(ByteBuffer buffer, boolean tryFree) {
if (tryFree) {
ByteBuffer oldBuffer = this.buffer;
if (oldBuffer != null) {
if (doNotFree) {
doNotFree = false;
} else {
freeDirect(oldBuffer);
}
}
}
this.buffer = buffer;
memoryAddress = PlatformDependent.directBufferAddress(buffer);
tmpNioBuf = null;
capacity = buffer.remaining();
}
這里tryFree
傳入的是false
,if條件不會(huì)進(jìn)入,后面的就是將分配的JDK的NIO包
下的直接內(nèi)存緩沖區(qū)DirectByteBuffer
賦值到Netty的UnpooledUnsafeDirectByteBuf類的buffer屬性
上,以及賦值直接內(nèi)存地址memoryAddress
,capacity
容量等屬性
至此,整個(gè)堆內(nèi)/堆外內(nèi)存分配流程都已經(jīng)分析完畢,至于Netty檢測(cè)直接內(nèi)存泄漏的原理后續(xù)文章會(huì)分析