Netty原始碼分析之ByteBuf(一)—ByteBuf中API及型別概述

bigfan發表於2020-08-31

ByteBuf是Netty中主要的資料容器與操作工具,也是Netty記憶體管理優化的具體實現,本章我們先從整體上對ByteBuf進行一個概述;

AbstractByteBuf是整個ByteBuf的框架類,定義了各種重要的標誌位與API供具體的實現類使用與實現;下面我們就從AbstractByteBuf類入手對ByteBuf的讀寫機制與API進行一個簡單的介紹

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractByteBuf.class);
    private static final String LEGACY_PROP_CHECK_ACCESSIBLE = "io.netty.buffer.bytebuf.checkAccessible";
    private static final String PROP_CHECK_ACCESSIBLE = "io.netty.buffer.checkAccessible";
    static final boolean checkAccessible; // accessed from CompositeByteBuf
    private static final String PROP_CHECK_BOUNDS = "io.netty.buffer.checkBounds";
    private static final boolean checkBounds;

    static {
        if (SystemPropertyUtil.contains(PROP_CHECK_ACCESSIBLE)) {
            checkAccessible = SystemPropertyUtil.getBoolean(PROP_CHECK_ACCESSIBLE, true);
        } else {
            checkAccessible = SystemPropertyUtil.getBoolean(LEGACY_PROP_CHECK_ACCESSIBLE, true);
        }
        checkBounds = SystemPropertyUtil.getBoolean(PROP_CHECK_BOUNDS, true);
        if (logger.isDebugEnabled()) {
            logger.debug("-D{}: {}", PROP_CHECK_ACCESSIBLE, checkAccessible);
            logger.debug("-D{}: {}", PROP_CHECK_BOUNDS, checkBounds);
        }
    }

    static final ResourceLeakDetector<ByteBuf> leakDetector =
            ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ByteBuf.class);

    int readerIndex;
    int writerIndex;
    private int markedReaderIndex;
    private int markedWriterIndex;
    private int maxCapacity;

在上面的程式碼中,我們需要知道ByteBu維護了兩個不同的索引readerIndex與writerIndex,這兩個索引預設都是從0開始,一個用於讀取,一個用於寫入。

 

當你從ByteBuf讀取資料時,readerIndex會遞增已經讀取的位元組數,同理當你寫入資料時,writerIndex也會隨之遞增。可以說ByteBuf中各種讀寫API都是基於readerIndex與writerIndex來控制的。從索引操作上,ByteBuf中API基本分為兩大部分,會引發索引值遞增的read(讀)和write(寫操作),反之不會引發索引值遞增的get或set操作。下面我們看下ByteBu中常用API的參考說明

ByteBuf API

 

read操作

readBoolean() 返回當前readIndex的Boolean值,readIndex增加1
readByte(int) 返回當前readIndex處的位元組值,readIndex增加1
readUnsignedByte() 返回當前readIndex處的無符號位元組值,readIndex增加1
readInt() 返回當前readIndex處的int值,readIndex增加4
readUnsignedInt() 返回當前readIndex處的無符號int值,返回型別為long,readIndex增加4
readLong() 返回當前readIndex處的long值,readIndex增加8
readShort()

返回當前readIndex處的short值,readIndex增加2

readUnsignedShort() 返回當前readIndex處的short值,readIndex增加2

 

 wirte操作

writeBoolean() 在當前writerIndex處寫入一個Boolean值(1或0),writerIndex增加1
writeByte(int) 在當前writerIndex處寫入一個byte值,writerIndex增加1
writeShort(int) 在當前writerIndex處寫入一個short值,writerIndex增加2
writeInt(int) 在當前writerIndex處寫入一個int值,writerIndex增加4
writeLong(int) 在當前writerIndex處寫入一個long值,writerIndex增加4

 

 get操作

getBoolean(int) 返回給的索引處的Boolean值,readIndex值不變
getByte(int) 返回給的索引處的Byte值,readIndex值不變
getShort(int) 返回給的索引處的short值,readIndex值不變
getInt(int) 返回給的索引處的int值,readIndex值不變
getLong(int) 返回給的索引處的long值,readIndex值不變

 

 set操作

setBoolean(int,boolean) 在指定索引處設定一個Boolean值(1或0),writerIndex值不變
setByte(int,int) 在指定索引處設定一個byte值,writerIndex值不變
setShort(int,int) 在指定索引處設定一個short值,writerIndex值不變
setInt(int,int) 在指定索引處設定一個int值,writerIndex值不變
setLong(int,int) 在指定索引處設定一個long值,writerIndex值不變

 

ByteBuf型別

Netty中ByteBuf相關實現類的UML圖

 

 Netty在構建ByteBuf時,有以下多種分類:

1、從記憶體型別上,分為堆記憶體與直接記憶體,HeapByteBuf與DirectByteBuf

我們知道Java中大部分物件都是在堆記憶體中儲存,由jvm統一管理,但NIO中 的 ByteBuffer 類允許 JVM 實現通過本地呼叫來分配記憶體,Netty中對基於直接記憶體的ByteBuffer也進行了封裝,這主要是為了避免在每次呼叫本地 I/O 操作之前(或者之後)將緩衝區的內容復 制到一箇中間緩衝區(或者從中間緩衝區把內容複製到緩衝區),直接緩衝區不會佔用堆的容量。事實上,在通過套接字傳送它之前,JVM將會在內部把你的緩衝 區複製到一個直接緩衝區中。所以如果使用直接緩衝區可以節約一次拷貝,提高IO操作效能。使用直接記憶體的優缺點如下:

(1)優點:由於資料直接在記憶體中,不存在從JVM拷貝資料到直接緩衝區的過程,提高IO操作效能。

(2)缺點:相對於基於堆的緩衝區,它們的分配和釋放都較為昂貴,同時由於直接記憶體不受JVM垃圾回收統一管理,需要自己手動回收,需要特別注意記憶體洩露的問題。

2、從分配模式上,分為池化與非池化;PooledByteBuf與UnpooledByteBuf

對於頻繁的申請與釋放記憶體帶來的效能損耗與碎片化問題,Netty基於池化思想通過預先申請一塊專用記憶體地址作為記憶體池進行管理,從而不需要每次都進行分配和釋放。

從上面的UML圖中可以看到,基於AbstractByteBuf父類針對直接記憶體與堆記憶體,也都有其對應的池化與非池化實現類;

PooledByteBuf  下有  PooledUnsafeDirectByteBuf、PooledHeapByteBuf、PooledDirectByteBuf三個子類實現

UnpooledByteBuf 下則是 UnpooledDirectByteBuf及其子類UnpooledUnsafeDirectByteBuf 與 UnpooledHeapByteBuf及其子類UnpooledUnsafeHeapByteBuf

3、從具體的操作類上,分為Unsafe與非Unsafe

 從上面的子類實現中,我們發現每種分類中又包含Usafe與非Unsafe的區別,我們知道java可以通過Unsafe類直接操作記憶體區域,所以這些類的區別就是在於是呼叫jdk的Unsafe直接去操作物件的記憶體地址還是通過jdk封裝的安全方式操作記憶體。

我們通過PooledByteBuf下Unsafe與非Unsafe實現類的getByte方法,看下具體的區別

PooledUnsafeHeapByteBuf

    @Override
    protected byte _getByte(int index) {
        return UnsafeByteBufUtil.getByte(addr(index));
    }

Netty中封裝了一個UnsafeByteBufUtil類,進入內部實現看到呼叫的是Unsafe物件進行具體操作

    static byte getByte(long address) {
        return UNSAFE.getByte(address);
    }


PooledHeapByteBuf

跟蹤其內部程式碼則可以看到位元組是基於陣列操作的

    @Override
    protected byte _getByte(int index) {
        return HeapByteBufUtil.getByte(memory, idx(index));
    }

    static byte getByte(byte[] memory, int index) {
        return memory[index];
    }

PooledDirectByteBuf

針對DirectByteBuf大家需要靈活理解,PooledUnsafeDirectByteBuf 與PooledUnsafeDirectByteBuf的區別一個是UnsafeByteBufUtil類直接操作,一個是使用java NIO中的DirectByteBuf類進行操作,但因為要操作直接記憶體,最後還都是要基於jdk的unsafe類實現;

對比他們兩種實現,可以看出Unsafe與非Unsafe的區別,前者是通過jdk的Unsafe類去運算元據,後者直接通過陣列或者jdk底層的DirectByteBuf去運算元據。

 

ByteBuf作為Netty中資料操作與記憶體分配的具體實現,是Netty中最為底層的也是最精細的一部分,本文只是對ByteBuf的一個簡單概述,後續我們會進一步對其進行探索與剖析,更好的展示Netty內部具體實現,希望對大家能有所幫助,其中如有不足與不正確的地方還望指出與海涵。

 

關注微信公眾號,檢視更多技術文章。

 

 

轉載說明:未經授權不得轉載,授權後務必註明來源(註明:來源於公眾號:架構空間, 作者:大凡)

 

相關文章