Netty源碼之ByteBuf(一)


什么是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的方式是使用PooledUnpooled類靜態(tài)方法,而不是通過(guò)調(diào)用構(gòu)造器的方式

這里以Unpooled類為例:

  • 創(chuàng)建一個(gè)初始容量為256最大容量Integer.MAX_VALUE\color{red}{堆內(nèi)}字節(jié)緩沖區(qū)
Unpooled.buffer(); 
  • 創(chuàng)建一個(gè)自定義初始容量大小最大容量Integer.MAX_VALUE\color{red}{堆內(nèi)}字節(jié)緩沖區(qū)
Unpooled.buffer(int initialCapacity); 
  • 創(chuàng)建一個(gè)初始容量為256最大容量Integer.MAX_VALUE\color{red}{堆外}直接字節(jié)緩沖區(qū)
Unpooled.directBuffer()
  • 創(chuàng)建一個(gè)自定義初始容量大小最大容量Integer.MAX_VALUE\color{red}{堆外}直接字節(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)支撐順序讀和寫操作,即readerIndexwriteIndex

我們用一張圖來(lái)展示一個(gè)緩沖區(qū)是如何通過(guò)這兩個(gè)指針變量被分割成三個(gè)區(qū)域

ByteBuf.png

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ū)的視圖,派生方法分非保留以及保留兩類

  • 非保留派生

  1. duplicate()

  2. slice()以及slice(int, int)

  3. readSlice()


  • 保留派生

  1. retainedDuplicate()

  2. retainedSlice()以及retainedSlice(int, int)

  3. readRetainedSlice(int)


注意以上方法只是返回一個(gè)已存在緩沖區(qū)的視圖而已,所謂視圖就是一種新的展示形式,一個(gè)派生的緩沖區(qū)將會(huì)有一個(gè)獨(dú)立的readerIndexwriterIndex以及標(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ò)ByteBufOutputStreamByteBufInputStream實(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 GCFull 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)存空間

\color{red}{Unpooled}

一個(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();
}

\color{red}{UnpooledByteBufAllocator}

一個(gè)非池化字節(jié)緩沖分配器

  • UML
UnpooledByteBufAllocator.png

接著看調(diào)用鏈,發(fā)現(xiàn)heapBuffer方法是其抽象父類AbstractByteBufAllocator實(shí)現(xiàn)的

\color{red}{AbstractByteBufAllocator}

該分配器抽象類實(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類圖
InstrumentedUnpooledUnsafeHeapByteBuf.png

可以看到其構(gòu)造函數(shù)就是調(diào)用父類的構(gòu)造,且傳入了this參數(shù),作為即將分配字節(jié)緩沖區(qū)的分配器引用,我們逐一查看

\color{red}{UnpooledHeapByteBuf}

大字節(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;
    }
  1. 調(diào)用父類UnpooledUnsafeHeapByteBuf類的分配字節(jié)數(shù)組方法
@Override
protected byte[] allocateArray(int initialCapacity) {
    return PlatformDependent.allocateUninitializedArray(initialCapacity);
}

可以看到這里借助了PlatformDependent類,分配了一個(gè)未初始化的字節(jié)數(shù)組

\color{red}{PlatformDependent}

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ù)組返回

  1. 這里主要看下所屬分配器的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占了多少

heapCounter.png

方法調(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)建圖

image.png

下面我們分析一下采用非池化直接內(nèi)存分配的字節(jié)緩沖區(qū)的創(chuàng)建流程

\color{red}{Unpooled}

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);
}
  1. 若initialCapacity和maxCapacity均為0,則返回一個(gè)空的buffer

  2. 校驗(yàn)參數(shù)合法性

  3. 轉(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)分析

  1. 利用PlatformDependent.hasUnsafe()判斷是否在classpath下存在sun.misc.Unsafe

  2. 關(guān)于noCleaner請(qǐng)看莫那一魯?shù)?/code>博友的Netty 內(nèi)存回收之 noCleaner 策略文章,這里為true,繼續(xù)往下走,調(diào)用InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf類的構(gòu)造函數(shù)

  3. 根據(jù)是否禁用泄露檢測(cè)disableLeakDetector,決定是否檢測(cè)泄露緩沖區(qū)

\color{red}{InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf}

同樣是UnpooledByteBufAllocator非池化字節(jié)緩沖分配器的內(nèi)部靜態(tài)類

  • UML
InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf.png
  • 源碼
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);
}

這里分三步分析

  1. 繼續(xù)向上調(diào)用父類AbstractByteBuf的構(gòu)造函數(shù),賦值maxCapacity屬性值

  2. 賦值自身屬性分配器alloc

  3. 這一步我們拆分為兩步走,第一步先調(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;
    }
  1. 中調(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;
    }
}

\color{red}{PlatformDependent0}

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):

  1. 增加直接內(nèi)存計(jì)數(shù)器

  2. 利用Unsafe類分配一個(gè)直接內(nèi)存地址,Long整數(shù)

  3. 調(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)存地址memoryAddresscapacity容量等屬性

DirectByteBuf.png

至此,整個(gè)堆內(nèi)/堆外內(nèi)存分配流程都已經(jīng)分析完畢,至于Netty檢測(cè)直接內(nèi)存泄漏的原理后續(xù)文章會(huì)分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。