Netty ByteBuf

ByteBuf

ByteBuf須要提供JDK ByteBuffer的功能(包含且不限於),主要有如下幾類基本功能:算法

  1. 7種Java基礎類型、byte[]、ByteBuffer(ByteBuf)的等的讀寫
  2. 緩衝區自身的copy和slice
  3. 設置網絡字節序
  4. 構造緩衝區實例
  5. 操做位置指針

擴容原理

  1. 首先確認ByteBuf是否已經被釋放,若是被釋放,則拋出IllegalReferenceCountException異常
  2. 判斷寫入須要的最小空間,若是該空間小於ByteBuf的可寫入空間,直接返回,不進行擴容
  3. 判斷寫入須要的最小空間,若是該空間大於ByteBuf的(最大容量-當前的寫索引),不進行擴容,拋出IndexOutOfBoundsException異常
  4. 計算新容量,動態擴容的規則,當新容量大於4MB時,以4MB的方式遞增擴容,在小於4MB時,從64字節開始倍增(Double)擴容

讀寫索引

Netty提供readIndex和writeIndex用來支持讀取和寫入操做,兩個索引將緩衝區劃分爲三個區域後端

  1. 0 ~ readIndex:已讀區域(可丟棄區域)
  2. readIndex ~ writeIndex:未讀取區域
  3. writeIndex ~ capacity:待寫入區域

已讀區域(Discardable Bytes)

位於已讀區域的內容代表該內容已被Netty處理完成,咱們能夠重用這塊緩衝區,儘可能減小緩衝區的動態擴容(複製,耗時操做)。數組

調用discardBytes()方法能夠清除已讀區域內容,但同時會致使未讀區域的左移,也是將未讀區域的內容複製到原來的已讀區域(耗時),
所以頻繁的調用discardBytes也是不可取的,能夠根據實際狀況進行調用。緩存

Readable Bytes和Writable Bytes

Readable Bytes(可讀空間)存儲的是未被讀取處理的內容,以read或者skip開頭的方法都會從readIndex開始讀取或者跳過指定的數據,同時readIndex會增長讀取或跳過
的字節數長度。若是讀取的字節數長度大於實際可讀取的字節數,拋出IndexOutOfBoundsException異常。安全

Writable Bytes(可寫入空間)是未被數據填充的緩衝區塊,以write開頭的操做都會從writeIndex開始向緩衝區寫入數據,同時writeIndex會增長寫入的數據的字節數長度。
若是寫入的字節數大於可寫入的字節數,會拋出IndexOutOfBoundsException異常。網絡

Clear

Clear操做並不會清除緩衝區的內容,只是將readIndex和writeIndex還原爲初始分配值。多線程

Mark和Reset

  1. markReadIndex
  2. resetReadIndex
  3. markWriteIndex
  4. resetWriteIndex

查找操做

  1. indexOf(int fromIndex, int toIndex, byte value):fromIndex<=toIndex時,從頭開始查找首次出現value的位置(查找範圍fromIndex ~ toIndex),當fromIndex > toIndex時,倒着查找首次出現value的位置(查找的範圍toIndex ~ fromIndex - 1),查不到返回-1
  2. bytesBefore(byte value):從ByteBuf的可讀區域中首次定位出現value的位置,沒有找到返回-1。該方法不會修改readIndex和writeIndex
  3. bytesBefore(int length, byte value):從ByteBuf的可讀區域中定位首次出現value的位置,結束索引是readIndex+length。若是length大於可讀字節數,拋出IndexOutOfBoundsException異常
  4. bytesBefore(int index, int length, byte value):從ByteBuf中定位首次出現value的位置,起始索引爲index,結束索引爲index+length,若是index+length大於當前緩衝區的容量,拋出IndexOutOfBoundsException異常
  5. forEachByte(int index, int length, ByteProcessor processor):從index開始,到index + length結束,與ByteProcessor設置的查找條件進行對比,知足條件,返回位置索引,不然返回-1
  6. forEachByteDesc(ByteProcessor processor):倒序遍歷ByteBuf的可讀字節數組,與ByteProcessor設置的查找條件進行對比,知足條件,返回位置索引,不然返回-1
  7. forEachByteDesc(int index, int length, ByteProcessor processor):以index + length - 1開始,直到index結束,倒序遍歷ByteBuf字節數組,與ByteProcessor設置的查找條件進行對比,知足條件,返回位置索引,不然返回-1

Netty提供了大量的默認的ByteProcessor,來對經常使用的查找本身進行查找,具體可見ByteProcessor接口。併發

Derived buffers(派生緩衝區)

  1. duplicate():返回當前ByteBuf的複製對象,複製後返回的ByteBuf與操做的ByteBuf共享緩衝區內容,可是維護本身獨立的讀寫索引。當修改複製後的ByteBuf內容後,原ByteBuf的內容也隨之改變,由於雙方持有的是同一個內容的指針引用。
  2. copy():複製一個新的ByteBuf對象,內容和索引都與原ByteBuf獨立,複製操做自己並不修改原ByteBuf的讀寫索引
  3. copy(int index, int length):複製一個新的ByteBuf對象,複製開始的索引爲index,複製的長度爲length
  4. slice():返回與當前ByteBuf的可讀子緩衝區,範圍是readIndex ~ writeIndex,返回後的ByteBuf與原ByteBuf內容共享,讀寫索引獨立維護,maxCapacity是當前ByteBuf的可讀字節數(換句話說就是這個新返回的緩衝區不能再進行寫入)
  5. slice(int index, int length):返回index開始,length長度的當前ByteBuf的子緩衝區,返回後的ByteBuf與原ByteBuf內容共享,讀寫索引獨立維護,maxCapacity是length(換句話說就是這個新返回的緩衝區不能再進行寫入)

轉換成標準的ByteBuffer

  1. ByteBuffer nioBuffer():將當前ByteBuf可讀的緩衝區轉換成ByteBuffer,二者共享同一個緩衝區內容引用,對ByteBuffer的讀寫操做並不會修改原ByteBuf的讀寫索引。返回後的ByteBuffer沒法感知ByteBuf的動態擴展。
  2. ByteBuffer nioBuffer(int index, int length):從ByteBuf的index位置開始長度爲length的緩衝區轉換成ByteBuffer,二者共享同一個緩衝區內容引用,對ByteBuffer的讀寫操做並不會修改原ByteBuf的讀寫索引。返回後的ByteBuffer沒法感知ByteBuf的動態擴展。

隨機讀寫

主要經過set和get開頭的方法,這兩個方法能夠指定索引位置。框架

ByteBuf源碼

從內存分配的角度來看,ByteBuf主要分爲如下兩類:性能

  1. 堆內存(HeapByteBuf)字節緩衝區:內存分配和回收速度快,能夠被JVM自動回收;缺點是若是Socket進行I/O讀寫,須要進行一次內存複製,將堆內存對應的緩衝區複製到內核Channel中,性能會有所降低
  2. 直接內存(DirectByteBuf)字節緩衝區:堆外內存直接分配,相比於堆內存,分配和回收速度比較慢,可是在Socket Channel中進行讀寫比較快(少一次內存複製)

ByteBuf的最佳時間是在I/O通訊線程的讀寫緩衝區使用DirectByteBuf,後端業務消息的編解碼模塊使用HeapByteBuf。

從內存回收的角度進行分類:

  1. 基於對象池的ByteBuf:本身維護了一個內存池,能夠重複利用ByteBuf對象,提高內存使用率,下降GC頻率
  2. 普通的ByteBuf

AbstractByteBuf

AbstractByteBuf繼承ByteBuf,ByteBuf中的一些公共屬性和方法會在AbstractByteBuf中實現。

主要變量

  1. ResourceLeakDetector<ByteBuf> leakDetector對象:被定義爲static,全部的ByteBuf實例共享一個ResourceLeakDetector<ByteBuf> leakDetector對象。ResourceLeakDetector主要用來檢測對象是否泄漏。
  2. 索引設置:讀寫索引、重置讀寫索引、最大容量

讀操做

讀操做的公共功能由父類實現,差別化由具體的子類實現。

選取readBytes(byte[] dst, int dstIndex, int length)分析:

  1. 首先對緩衝區可讀空間進行校驗:若是讀取的長度(length) < 0,會拋出IllegalArgumentException異常;若是可讀的字節數小於須要讀取的長度(length),會拋出IndexOutOfBoundsException異常
  2. 校驗經過以後,調用getBytes方法從當前的讀索引開始進行讀取(這一塊就須要由真正的子類來各自實現),複製length個字節到目標byte數組,數組開始的位置是dstIndex
  3. 讀取成功後,對讀索引進行遞增,增長的長度爲length

寫操做

寫操做的公共功能由父類實現,差別化由具體的子類實現。

選取writeBytes(byte[] src, int srcIndex, int length)分析:

  1. 首先對緩衝區的可寫空間進行校驗:若是要寫入的長度(length) < 0,會拋出IllegalArgumentException異常;若是要寫入的長度小於緩衝區可寫入的字節數,代表可寫;若是要寫入的長度 > 最大容量 - writeIndex,會拋出IndexOutOfBoundsException;不然進行擴容操做(擴容操做的原理前面已經講過)。

操做索引

與索引相關的操做主要涉及設置讀寫索引、mark、和reset等。

選取readerIndex(int readerIndex)進行分析:

  1. 首先對索引合法性進行判斷:若是readerIndex小於0或者readerIndex > writeIndex,則拋出IndexOutOfBoundsException異常
  2. 校驗經過以後,將讀索引設置爲readerIndex

重用緩衝區

選取discardReadBytes()進行分析:

  1. 若是readIndex等於0,直接返回
  2. 若是readIndex和writeIndex不相等,首先調用setBytes(int index, ByteBuf src, int srcIndex, int length)方法進行字節數組的複製,

而後從新設置markReadIndex、markWriteIndex、readIndex和writeIndex

  1. 若是readIndex等於writeIndex,調整markReadIndex和markWriteIndex,不進行字節數組複製,設置readIndex=writeIndex=0

skipBytes

  1. 校驗跳過的字節長度:若是跳過的字節長度小於0,則拋出IllegalArgumentException異常,若是跳過的字節數大於可讀取的字節數,則拋出IndexOutOfBoundsException異常
  2. 校驗經過以後,readIndex增長跳過的字節長度

AbstractReferenceCountedByteBuf

該類主要是對引用進行計數,相似於JVM內存回收的對象引用計數器,用於跟蹤對象的分配和銷燬,作自動內存回收。

成員變量

  1. AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater對象:經過原子的方式對成員變量進行更新操做,實現線程安全,消除鎖。
  2. volatile int refCnt:用於跟蹤對象的引用次數,使用volatile是爲了解決多線程併發訪問的可見性問題。

對象引用計數器

每調用retain()方法一次,引用計數器就會加1,但加完以後會對數據進行校驗,具體的校驗內容以下:
若是加1以前的引用次數小於等於0或者原來的引用次數 + 增長的次數 < 原來的引用次數,則須要還原此次引用計數器增長操做,而且拋出IllegalReferenceCountException異常

UnpooledHeapByteBuf

UnpooledHeapByteBuf是基於堆內存分配的字節緩衝區,每次I/O讀寫都會建立一個新的UnpooledHeapByteBuf。

成員變量

  1. ByteBufAllocator alloc:用於UnpooledHeapByteBuf的內存分配
  2. byte[] array:緩衝區數組,此處也可用ByteBuffer,可是用byte數組的緣由是提高性能和更加便捷的進行位操做
  3. ByteBuffer tmpNioBuf:用於實現Netty的ByteBuf到JDK NIO ByteBuffer的轉換

動態擴展緩衝區

  1. 校驗新容量:若是新容量小於0或者新容量大於最大容量,拋出IllegalArgumentException異常,不然校驗經過
  2. 若是新容量大於舊容量,使用new byte[newCapacity]建立新的緩衝數組,而後經過System.arraycopy進行復制,將舊的緩衝區內容拷貝到新的緩衝區中,最後在ByteBuf中替換舊的數組,而且將原來的ByteBuffer tmpNioBuf置爲空
  3. 若是新容量小於舊容量,使用new byte[newCapacity]建立新的緩衝數組,若是讀索引小於新容量(若是寫索引大於新容量,將寫索引直接置爲新容量),而後經過System.arraycopy將當前可讀的緩衝區內容複製到新的byte數組,若是讀索引大於新容量,說明沒有能夠拷貝的緩衝區,直接將讀寫索引置爲新容量,而且使用新的byte數組替換原來的字節數組

字節數組複製

setBytes(int index, byte[] src, int srcIndex, int length)

  1. 首先是合法性校驗,先是校驗index,length,若是這兩個值有小於0,或者相加小於0,或者兩個相加大於ByteBuf的容量,則拋出IndexOutOfBoundsException異常,接着校驗被複制的數組的長度和索引問題(srcIndex、length),若是srcIndex、length小於0,或者兩個相加小於0,或者兩個相加超過了src字節數組的容量,也拋出IndexOutOfBoundsException異常
  2. 校驗經過以後,使用System.arraycopy方法進行字節數組的拷貝

ByteBuf以get和set開頭讀寫緩衝區的方法不會修改讀寫索引

轉換成JDK ByteBuffer

因爲UnpooledHeapByteBuf緩衝區採用了byte數組實現,一樣的ByteBuffer底層也是用了byte數組實現,同時ByteBuffer還提供了wrap方法,
直接將字節數組轉換成ByteBuffer,最後調用slice方法。因爲每次調用都會建立一個新的ByteBuffer,所以起不到重用緩衝區內容的效果。

子類實現相關的方法

  1. hasArray():是否支持數組,判斷緩衝區的實現是否基於字節數組
  2. array():若是緩衝區的實現基於字節數組,返回字節數組

PooledByteBuf

PoolArena

Arena是指一塊區域,在內存管理中,Memory Arena指內存中的一大塊連續的區域,PoolArena是Netty的內存池實現類。

爲了集中管理內存的分配和釋放,同時提升分配和釋放內存的性能,框架會預先申請一大塊內存,而後經過提供相應的分配和釋放接口來使用內存。因爲再也不使用系統調用來申請和釋放內存,
應用或者系統的性能大大提升。預先申請的那一大塊內存稱之爲Memory Arena。

Netty的PoolArena是由多個Chunk組成的大塊內存區域,每一個Chunk由一個或者多個Page組成。所以,對內存的組織管理主要集中在如何組織管理Chunk和Page。

PoolChunk

Chunk主要用來組織和管理多個Page的內存分配。Netty中,Chunk中的Page被構形成一棵二叉樹。

每個Page能夠成爲一個節點,第一層的Page節點用來分配全部Page須要的內存。每一個節點記錄了本身在Memory Arena中的偏移地址,當一個節點表明的內存區域被分配出去之後,
該節點會被標記爲已分配,從這個節點往下的全部節點在後面的內存分配請求中都會被忽略。

在內存分配查找節點時,對樹的遍歷採用深度優先的算法,但在選擇在哪一個子節點繼續遍歷時則是隨機的,並不老是訪問左邊的子節點。

PoolSubpage

對於小於一個Page的內存,Netty在Page中完成分配。每一個Page會被切分紅大小相等的多個存儲塊,存儲塊的大小由第一次申請的內存塊大小決定。

一個Page只能用於分配與第一次申請時大小相同的內存。

Page中存儲區域的使用狀態經過一個long數組來維護,數組中每一個long的每一位表示一個塊存儲區域的佔用狀況:0表示未佔用,1表示已佔用。

內存回收策略

Chunk和Page都經過狀態位來標識內存是否可用,不一樣的是Chunk經過在二叉樹上對節點進行標識實現,Page是經過維護塊的使用狀態標識來實現。

PooledDirectByteBuf

PooledDirectByteBuf基於內存池實現。

建立字節緩衝區實例

新建立PooledDirectByteBuf對象不能直接new,而是從內存池Recycler<PooledDirectByteBuf>中獲取,而後設置引用計數器的值爲1,設置緩衝區的最大空間,
設置讀寫索引、標記讀寫索引爲0。

複製新的字節緩衝區實例

copy(int index, int length)方法能夠複製一個ByteBuf實例,而且與原來的ByteBuf相互獨立。

  1. 首先校驗索引和長度的合法性
  2. 校驗經過以後,調用PooledByteBufAllocator分配一個新的ByteBuf,最終會調用PooledByteBufAllocator中的newDirectBuffer(int initialCapacity, int maxCapacity)方法進行內存的分配
  3. 在newDirectBuffer中,直接從緩存中獲取ByteBuf而不是建立一個新的對象

ByteBuf輔助類

ByteBufHolder

ByteBufHolder是BytBuf容器。好比,Http協議的請求消息和應答消息均可以攜帶消息體,這個消息體在Netty中就是ByteBuf對象。因爲不一樣的協議消息體能夠包含不一樣的
協議字段和功能,所以須要對ByteBuf進行包裝和抽象,爲了知足這些定製化的需求,Netty抽象出了ByteBufHolder對象。

ByteBufAllocator

ByteBufAllocator是字節緩衝區分配器,按照Netty緩衝區的實現不一樣能夠分爲:基於內存池的字節緩衝區分配器和普通的字節緩衝區分配器。

方法名稱 返回值說明 功能說明
buffer() ByteBuf 分配一個字節緩衝區,緩衝區的類型由ByteBufAllocator的實現類決定
buffer(int initialCapacity) ByteBuf 分配一個初始容量爲initialCapacity的字節緩衝區,緩衝區的類型由ByteBufAllocator的實現類決定
buffer(int initialCapacity, int maxCapacity) ByteBuf 分配一個初始容量爲initialCapacity,最大容量爲maxCapacity的字節緩衝區,緩衝區的類型由ByteBufAllocator的實現類決定
ioBuffer(int initialCapacity, int maxCapacity) ByteBuf 分配一個初始容量爲initialCapacity,最大容量爲maxCapacity的Direct Buffer,Direct Buffer I/O性能高
heapBuffer(int initialCapacity, int maxCapacity) ByteBuf 分配一個初始容量爲initialCapacity,最大容量爲maxCapacity的Heap Buffer
directBuffer(int initialCapacity, int maxCapacity) ByteBuf 分配一個初始容量爲initialCapacity,最大容量爲maxCapacity的Direct Buffer
compositeBuffer(int maxNumComponents) CompositeByteBuf 分配一個最多包含maxNumComponents個緩衝區的複合緩衝區,緩衝區的類型由ByteBufAllocator的實現類決定
isDirectBufferPooled() boolean 是否使用了直接內存池
calculateNewCapacity(int minNewCapacity, int maxCapacity) int 動態擴容時計算新容量

CompositeByteBuf

CompositeByteBuf容許將多個ByteBuf的實例組裝到一塊兒。

CompositeByteBuf定義了一個Component類型的集合,Component其實是ByteBuf的包裝實現類,它聚合了ByteBuf對象,維護ByteBuf在集合中的位置偏移量等信息。

CompositeByteBuf支持動態增長(addComponent(ByteBuf buffer))和刪除(removeComponent(int cIndex))ByteBuf,增長或刪除ByteBuf以後,
須要更新各個ByteBuf的索引偏移量。

ByteBufUtil

ByteBufUtil提供了大量的靜態方法來操做ByteBuf。列舉三個:

  1. ByteBuf encodeString(ByteBufAllocator alloc, CharBuffer src, Charset charset):對須要編碼的字符串src按照指定的字符集charset進行編碼,利用指定的ByteBufAllocator生成一個ByteBuf
  2. decodeString(ByteBuf src, int readerIndex, int len, Charset charset):從指定索引readIndex開始日後len個字節長度,對ByteBuf對象src按照指定的字符集charset進行解碼
  3. hexDump(ByteBuf buffer):將ByteBuf對象的參數內容以十六進制的格式輸出
相關文章
相關標籤/搜索