自頂向下深入分析Netty(九)--UnpooledByteBuf源碼分析

前文分析了ByteBuf的抽象類實(shí)現(xiàn)框架甘邀,現(xiàn)在開始分析最底層的實(shí)現(xiàn)類凝赛。分為兩種情形:Unpooled和Pooled窿侈,首先看Unpooled。

1.UnpooledHeapByteBuf

該Bytebuf的底層為不使用對(duì)象池技術(shù)的JAVA堆字節(jié)數(shù)組淤井,首先看其中的成員變量:

    private final ByteBufAllocator alloc;   // 分配器
    byte[] array;   // 底層字節(jié)數(shù)組
    private ByteBuffer tmpNioBuf; // NIO的ByteBuffer形式

只需要著重關(guān)注array變量,它是位于JAVA堆的字節(jié)數(shù)組摊趾。
再看一個(gè)構(gòu)造方法(忽略其中的參數(shù)檢查):

    protected UnpooledHeapByteBuf(ByteBufAllocator alloc, 
                                    int initialCapacity, int maxCapacity) {
        super(maxCapacity);

        this.alloc = alloc;
        setArray(allocateArray(initialCapacity));
        setIndex(0, 0);
    }
    
    private void setArray(byte[] initialArray) {
        array = initialArray;
        tmpNioBuf = null;
    }
    
    byte[] allocateArray(int initialCapacity) {
        return new byte[initialCapacity];
    }

實(shí)現(xiàn)也很簡(jiǎn)單币狠,只需關(guān)注allocateArray()方法,分配一個(gè)數(shù)組砾层;對(duì)應(yīng)地漩绵,有一個(gè)freeArray()方法,釋放一個(gè)數(shù)組梢为,代碼如下:渐行、

    void freeArray(byte[] array) {
        // NOOP 
    }

由于堆內(nèi)的字節(jié)數(shù)組會(huì)被GC自動(dòng)回收,所以不需要具體實(shí)現(xiàn)代碼铸董。此外祟印,在引用計(jì)數(shù)的分析中,當(dāng)引用計(jì)數(shù)釋放的時(shí)候需要調(diào)用deallocate()方法釋放該ByteBuf粟害,實(shí)現(xiàn)如下:

    protected void deallocate() {
        freeArray(array);
        array = null;
    }

同理蕴忆,使用GC自動(dòng)回收,而設(shè)置array=null可以幫助GC回收悲幅。
ByteBuf中有關(guān)于判斷底層實(shí)現(xiàn)的方法套鹅,具體實(shí)現(xiàn)也很簡(jiǎn)單:

    // 默認(rèn)的字節(jié)序:大端模式
    public ByteOrder order() { return ByteOrder.BIG_ENDIAN; }
    
    // 底層是否有JAVA堆字節(jié)數(shù)組
    public boolean hasArray() { return true; }
    
    // 底層數(shù)組的偏移量
    public int arrayOffset() { return 0; }

    // 是否直接數(shù)組
    public boolean isDirect() { return false; }
    
    // 是否含有os底層的數(shù)組起始地址
    public boolean hasMemoryAddress() { return false; }

接下來(lái),看重要的設(shè)置容量方法capacity(int newCapacity)

    public ByteBuf capacity(int newCapacity) {
        int oldCapacity = array.length;
        byte[] oldArray = array;
        if (newCapacity > oldCapacity) {    // 容量擴(kuò)增
            byte[] newArray = allocateArray(newCapacity); // 申請(qǐng)數(shù)組
            // 將老數(shù)組的字節(jié)復(fù)制到新數(shù)組
            System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
            setArray(newArray);
            freeArray(oldArray);
        } else if (newCapacity < oldCapacity) { // 容量縮減
            byte[] newArray = allocateArray(newCapacity);
            int readerIndex = readerIndex();
            // 容量縮減導(dǎo)致讀寫索引改變
            if (readerIndex < newCapacity) {
                int writerIndex = writerIndex();
                if (writerIndex > newCapacity) {
                    writerIndex(writerIndex = newCapacity);
                }
                // 只拷貝讀索引之后的數(shù)據(jù)汰具,讀索引之前0填充
                System.arraycopy(oldArray, readerIndex, 
                                 newArray, readerIndex, writerIndex - readerIndex);
            } else {
                setIndex(newCapacity, newCapacity);
            }
            setArray(newArray);
            freeArray(oldArray);
        }
        // 容量相等時(shí)不做處理
        return this;
    }

設(shè)置容量分為兩種情況:容量擴(kuò)增和容量縮減卓鹿。實(shí)現(xiàn)都是將老數(shù)據(jù)復(fù)制到新的字節(jié)數(shù)組中,有必要的話留荔,調(diào)整讀寫索引位置吟孙。
之前分析過(guò)getXXX()readXXX()的核心實(shí)現(xiàn)是_getXXX(index)方法,以_getInt(index)為例進(jìn)行分析聚蝶,代碼如下:

    protected int _getInt(int index) {
        return HeapByteBufUtil.getInt(array, index);
    }
    
    static int getInt(byte[] memory, int index) {
        return  (memory[index]     & 0xff) << 24 |
                (memory[index + 1] & 0xff) << 16 |
                (memory[index + 2] & 0xff) <<  8 |
                memory[index + 3] & 0xff;
    }

將字節(jié)數(shù)組中指定索引位置處的4個(gè)字節(jié)按照大端模式通過(guò)移位組裝為一個(gè)整數(shù)杰妓。同理,可推斷_setInt(index)方法將一個(gè)整數(shù)的4個(gè)字節(jié)通過(guò)移位填充到字節(jié)數(shù)組的指定位置碘勉,確實(shí)如此巷挥,核心實(shí)現(xiàn)如下:

    static void setInt(byte[] memory, int index, int value) {
        memory[index]     = (byte) (value >>> 24);
        memory[index + 1] = (byte) (value >>> 16);
        memory[index + 2] = (byte) (value >>> 8);
        memory[index + 3] = (byte) value;
    }

可以派生新的ByteBuf的方法中,slice()duplicate()共享底層實(shí)現(xiàn)验靡,在本類中倍宾,就是共享array變量雏节,但各自維護(hù)獨(dú)立索引,而copy()方法有自己獨(dú)立的底層字節(jié)數(shù)組高职,通過(guò)將數(shù)據(jù)復(fù)制到一個(gè)新的字節(jié)數(shù)組實(shí)現(xiàn)矾屯,代碼如下:

    public ByteBuf copy(int index, int length) {
        checkIndex(index, length);
        byte[] copiedArray = new byte[length];
        System.arraycopy(array, index, copiedArray, 0, length);
        return new UnpooledHeapByteBuf(alloc(), copiedArray, maxCapacity());
    }

雖然JDK自帶的ByteBuffer有各種缺憾,但在進(jìn)行IO時(shí)初厚,不得不使用原生的ByteBuffer件蚕,所以Netty的ByteBuf也提供方法轉(zhuǎn)化,實(shí)現(xiàn)如下:

    public ByteBuffer internalNioBuffer(int index, int length) {
        checkIndex(index, length);
        return (ByteBuffer) internalNioBuffer().clear()
                                .position(index).limit(index + length);
    }
    
    private ByteBuffer internalNioBuffer() {
        ByteBuffer tmpNioBuf = this.tmpNioBuf;
        if (tmpNioBuf == null) {
            this.tmpNioBuf = tmpNioBuf = ByteBuffer.wrap(array);
        }
        return tmpNioBuf;
    }

方法將該類轉(zhuǎn)化為JDK的HeapByteBuffer产禾,可見也是一個(gè)堆緩沖區(qū)排作。clear().position(index).limit(index + length)的使用是防止原生ByteBuffer的讀寫模式切換造成的錯(cuò)誤。

至此亚情,UnpooledHeapByteBuf的實(shí)現(xiàn)分析完畢妄痪,可見并沒有想象中的困難,再接再厲楞件,分析UnpooledDirectByteBuf衫生。

2. UnpooledDirectByteBuf

Netty的UnpooledDirectByteBuf在NIO的DirectByteBuf上采用組合的方式進(jìn)行了封裝,屏蔽了對(duì)程序員不友好的地方土浸,并使其符合Netty的ByteBuf體系罪针。使用與UnpooledHeapByteBuf相同的順序進(jìn)行分析,首先看成員變量:

    private final ByteBufAllocator alloc;   // 分配器

    private ByteBuffer buffer;  // 底層NIO直接ByteBuffer
    private ByteBuffer tmpNioBuf; // 用于IO操作的ByteBuffer
    private int capacity; // ByteBuf的容量
    private boolean doNotFree; // 釋放標(biāo)記

做一個(gè)簡(jiǎn)介黄伊,buffer表示底層的直接ByteBuffer泪酱;tmpNioBuf常用來(lái)進(jìn)行IO操作,實(shí)現(xiàn)實(shí)質(zhì)是buffer.duplicate()即與buffer共享底層數(shù)據(jù)結(jié)構(gòu)还最;capacity表示緩沖區(qū)容量墓阀,即字節(jié)數(shù);doNotFree是一個(gè)標(biāo)記拓轻,表示是否需要釋放buffer的底層內(nèi)存斯撮。

接著分析構(gòu)造方法:

    protected UnpooledDirectByteBuf(ByteBufAllocator alloc, 
                                int initialCapacity, int maxCapacity) {
        super(maxCapacity);

        this.alloc = alloc;
        setByteBuffer(allocateDirect(initialCapacity));
    }
    
    protected ByteBuffer allocateDirect(int initialCapacity) {
        return ByteBuffer.allocateDirect(initialCapacity);
    }
    
    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();
    }

由于setByteBuffer(buffer)中含有doNotFree變量使得理解稍微困難.仔細(xì)分析,當(dāng)doNotFree為true時(shí)扶叉,調(diào)用后置為false勿锅,而為false時(shí)都需要freeDirect(oldBuffer)。由此可知辜梳,doNotFree表示不需要釋放舊的Buffer粱甫,根據(jù)代碼大全泳叠,使用反義Not并不是好的做法作瞄,使用free表示是否需要釋放舊的Buffer會(huì)更容易讓人理解。另外從代碼可以看出:不需要釋放舊的Buffer只有一種情況危纫,這種情況便是Buffer作為構(gòu)造方法的參數(shù)時(shí)宗挥,代碼如下:

    protected UnpooledDirectByteBuf(ByteBufAllocator alloc, 
                                      ByteBuffer initialBuffer, int maxCapacity) {
        super(maxCapacity);
        int initialCapacity = initialBuffer.remaining();

        this.alloc = alloc;
        doNotFree = true;   // 置為true 表示不需要釋放原有buffer
        setByteBuffer(initialBuffer.slice().order(ByteOrder.BIG_ENDIAN));
        // 此時(shí) doNotFree已經(jīng)為false
        writerIndex(initialCapacity);
    }

分析完乌庶,發(fā)現(xiàn)doNotFree是一個(gè)不必要的變量,除非在執(zhí)行構(gòu)造方法的時(shí)候契耿,oldBuffer不為null瞒大。(目前沒想到有什么情況如此)
使用allocateDirect(initialCapacity)分配內(nèi)存時(shí)實(shí)際委托給NIO的方法,釋放內(nèi)存freeDirect(buffer)也如此搪桂,委托給了NIO中DirectByteBuffer的cleaner透敌,代碼如下:

    protected void freeDirect(ByteBuffer buffer) {
        PlatformDependent.freeDirectBuffer(buffer);
    }
    
    public void freeDirectBuffer(ByteBuffer buffer) {
        if (!buffer.isDirect()) {
            return;
        }
        try {
            Object cleaner = PlatformDependent0.getObject(buffer, CLEANER_FIELD_OFFSET);
            if (cleaner != null) {
                CLEAN_METHOD.invoke(cleaner);
            }
        } catch (Throwable cause) {
            PlatformDependent0.throwException(cause);
        }
    }

實(shí)際代碼根據(jù)JDK版本不同調(diào)用不同方法,上述只是其中之一踢械,但原理相同酗电,不再列出。
與引用計(jì)數(shù)相關(guān)的deallocate()方法内列,代碼實(shí)現(xiàn)如下:

    protected void deallocate() {
        ByteBuffer buffer = this.buffer;
        if (buffer == null) {
            return;
        }

        this.buffer = null;

        if (!doNotFree) { 
            freeDirect(buffer); // 前述分析可知撵术,doNotFree構(gòu)造方法之后一直為false
        }
    }

判斷底層實(shí)現(xiàn)的方法則如下:

    // 默認(rèn)的字節(jié)序:大端模式
    public ByteOrder order() { return ByteOrder.BIG_ENDIAN; }
    
    // 是否直接數(shù)組
    public boolean isDirect() { return true; }
    
    // 底層是否有JAVA堆字節(jié)數(shù)組
    public boolean hasArray() { throw new UnsupportedOperationException("..."); }
    
    // 底層數(shù)組的偏移量
    public int arrayOffset() { throw new UnsupportedOperationException("..."); }

    // 是否含有os底層的數(shù)組起始地址
    public boolean hasMemoryAddress() { return false; }

設(shè)置容量的方法:

    public ByteBuf capacity(int newCapacity) {
        checkNewCapacity(newCapacity);

        int readerIndex = readerIndex();
        int writerIndex = writerIndex();

        int oldCapacity = capacity;
        if (newCapacity > oldCapacity) {    // 容量擴(kuò)增
            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;
    }

與HeapByteBuf類似,容量改變時(shí)话瞧,都將oldBuffer中的數(shù)據(jù)復(fù)制到新的newBuffer中嫩与,只是在容量縮減時(shí),需要調(diào)整讀寫索引交排。
接著看關(guān)鍵的_getInt(index)_setInt(index,value)方法:

    protected int _getInt(int index) {
        return buffer.getInt(index);
    }
    
    protected void _setInt(int index, int value) {
        buffer.putInt(index, value);
    }

可見具體實(shí)現(xiàn)委托給了NIO原生的ByteBuffer划滋,追蹤其中的具體實(shí)現(xiàn),一種情況下的實(shí)現(xiàn)如下:

    
    static int getIntB(long a) {
        return makeInt(_get(a    ),
                       _get(a + 1),
                       _get(a + 2),
                       _get(a + 3));
    }
    
    static private int makeInt(byte b3, byte b2, byte b1, byte b0) {
        return (((b3       ) << 24) |
                ((b2 & 0xff) << 16) |
                ((b1 & 0xff) <<  8) |
                ((b0 & 0xff)      ));
    }

可見與Netty的HeapByteBuf實(shí)現(xiàn)一致埃篓。另一種情況是native實(shí)現(xiàn)古毛,沒有找到具體實(shí)現(xiàn)代碼,如果你有興趣可以尋找相關(guān)實(shí)現(xiàn)都许,有相關(guān)發(fā)現(xiàn)請(qǐng)告訴我稻薇。
繼續(xù)看copy()方法:

    public ByteBuf copy(int index, int length) {
        ensureAccessible();
        ByteBuffer src;
        try {
            src = (ByteBuffer) buffer.duplicate()
                        .clear().position(index).limit(index + length);
        } catch (IllegalArgumentException ignored) {
            throw new IndexOutOfBoundsException(
                    "Too many bytes to read - Need " + (index + length));
        }

        return alloc().directBuffer(length, maxCapacity()).writeBytes(src);
    }

對(duì)原buffer使用duplicate()方法,從而不干擾原來(lái)buffer的索引胶征。然后從分配器中申請(qǐng)一個(gè)buffer并寫入原buffer的數(shù)據(jù)塞椎。
最后看internalNioBuffer()

    public ByteBuffer internalNioBuffer(int index, int length) {
        checkIndex(index, length);
        return (ByteBuffer) internalNioBuffer()
                    .clear().position(index).limit(index + length);
    }

    private ByteBuffer internalNioBuffer() {
        ByteBuffer tmpNioBuf = this.tmpNioBuf;
        if (tmpNioBuf == null) {
            this.tmpNioBuf = tmpNioBuf = buffer.duplicate();
        }
        return tmpNioBuf;
    }

可見,與copy()相同睛低,使用duplicate()防止干擾原buffer的索引案狠。
至此,UnpooledDirectByteBuf的源碼分析完畢钱雷。

3. UnsafeByteBuf

Netty還使用JAVA的后門類sun.misc.Unsafe實(shí)現(xiàn)了兩個(gè)緩沖區(qū)UnpooledUnsafeHeapByteBufUnpooledUnsafeDirectByteBuf骂铁。這個(gè)強(qiáng)大的后門類Unsafe可以暴露出對(duì)象的底層地址,一般不建議使用罩抗,而性能優(yōu)化狂魔Netty則顧不得這些拉庵。簡(jiǎn)單介紹一下這兩個(gè)類的原理,不再對(duì)代碼進(jìn)行分析套蒂。UnpooledUnsafeHeapByteBuf在使用Unsafe后钞支,暴露出字節(jié)數(shù)組在JAVA堆中的地址茫蛹,所以不再使用字節(jié)數(shù)組的索引即array[index]訪問(wèn),轉(zhuǎn)而使用baseAddress + Index的得到字節(jié)的地址烁挟,然后從該地址取得字節(jié)婴洼。UnpooledUnsafeDirectByteBuf也一樣,暴露底層DirectByteBuffer的地址后撼嗓,使用相同的Address + Index方式取得對(duì)應(yīng)字節(jié)柬采。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市且警,隨后出現(xiàn)的幾起案子警没,更是在濱河造成了極大的恐慌,老刑警劉巖振湾,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杀迹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡押搪,警方通過(guò)查閱死者的電腦和手機(jī)树酪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)大州,“玉大人续语,你說(shuō)我怎么就攤上這事∠没” “怎么了疮茄?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)根暑。 經(jīng)常有香客問(wèn)我力试,道長(zhǎng),這世上最難降的妖魔是什么排嫌? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任畸裳,我火速辦了婚禮,結(jié)果婚禮上淳地,老公的妹妹穿的比我還像新娘怖糊。我一直安慰自己,他們只是感情好颇象,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布伍伤。 她就那樣靜靜地躺著,像睡著了一般遣钳。 火紅的嫁衣襯著肌膚如雪扰魂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音阅爽,去河邊找鬼。 笑死荐开,一個(gè)胖子當(dāng)著我的面吹牛付翁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播晃听,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼百侧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了能扒?” 一聲冷哼從身側(cè)響起佣渴,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎初斑,沒想到半個(gè)月后辛润,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡王带,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年留夜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了罪治。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡乎澄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出测摔,到底是詐尸還是另有隱情置济,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布锋八,位于F島的核電站浙于,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏挟纱。R本人自食惡果不足惜路媚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望樊销。 院中可真熱鬧整慎,春花似錦、人聲如沸围苫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)剂府。三九已至拧揽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背淤袜。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工痒谴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铡羡。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓积蔚,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親烦周。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尽爆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容