ByteBuf
ByteBuf須要提供JDK ByteBuffer的功能(包含且不限於),主要有如下幾類基本功能:算法
- 7種Java基礎類型、byte[]、ByteBuffer(ByteBuf)的等的讀寫
- 緩衝區自身的copy和slice
- 設置網絡字節序
- 構造緩衝區實例
- 操做位置指針
擴容原理
- 首先確認ByteBuf是否已經被釋放,若是被釋放,則拋出IllegalReferenceCountException異常
- 判斷寫入須要的最小空間,若是該空間小於ByteBuf的可寫入空間,直接返回,不進行擴容
- 判斷寫入須要的最小空間,若是該空間大於ByteBuf的(最大容量-當前的寫索引),不進行擴容,拋出IndexOutOfBoundsException異常
- 計算新容量,動態擴容的規則,當新容量大於4MB時,以4MB的方式遞增擴容,在小於4MB時,從64字節開始倍增(Double)擴容
讀寫索引
Netty提供readIndex和writeIndex用來支持讀取和寫入操做,兩個索引將緩衝區劃分爲三個區域後端
- 0 ~ readIndex:已讀區域(可丟棄區域)
- readIndex ~ writeIndex:未讀取區域
- 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
- markReadIndex
- resetReadIndex
- markWriteIndex
- resetWriteIndex
查找操做
- indexOf(int fromIndex, int toIndex, byte value):fromIndex<=toIndex時,從頭開始查找首次出現value的位置(查找範圍fromIndex ~ toIndex),當fromIndex > toIndex時,倒着查找首次出現value的位置(查找的範圍toIndex ~ fromIndex - 1),查不到返回-1
- bytesBefore(byte value):從ByteBuf的可讀區域中首次定位出現value的位置,沒有找到返回-1。該方法不會修改readIndex和writeIndex
- bytesBefore(int length, byte value):從ByteBuf的可讀區域中定位首次出現value的位置,結束索引是readIndex+length。若是length大於可讀字節數,拋出IndexOutOfBoundsException異常
- bytesBefore(int index, int length, byte value):從ByteBuf中定位首次出現value的位置,起始索引爲index,結束索引爲index+length,若是index+length大於當前緩衝區的容量,拋出IndexOutOfBoundsException異常
- forEachByte(int index, int length, ByteProcessor processor):從index開始,到index + length結束,與ByteProcessor設置的查找條件進行對比,知足條件,返回位置索引,不然返回-1
- forEachByteDesc(ByteProcessor processor):倒序遍歷ByteBuf的可讀字節數組,與ByteProcessor設置的查找條件進行對比,知足條件,返回位置索引,不然返回-1
- forEachByteDesc(int index, int length, ByteProcessor processor):以index + length - 1開始,直到index結束,倒序遍歷ByteBuf字節數組,與ByteProcessor設置的查找條件進行對比,知足條件,返回位置索引,不然返回-1
Netty提供了大量的默認的ByteProcessor,來對經常使用的查找本身進行查找,具體可見ByteProcessor接口。併發
Derived buffers(派生緩衝區)
- duplicate():返回當前ByteBuf的複製對象,複製後返回的ByteBuf與操做的ByteBuf共享緩衝區內容,可是維護本身獨立的讀寫索引。當修改複製後的ByteBuf內容後,原ByteBuf的內容也隨之改變,由於雙方持有的是同一個內容的指針引用。
- copy():複製一個新的ByteBuf對象,內容和索引都與原ByteBuf獨立,複製操做自己並不修改原ByteBuf的讀寫索引
- copy(int index, int length):複製一個新的ByteBuf對象,複製開始的索引爲index,複製的長度爲length
- slice():返回與當前ByteBuf的可讀子緩衝區,範圍是readIndex ~ writeIndex,返回後的ByteBuf與原ByteBuf內容共享,讀寫索引獨立維護,maxCapacity是當前ByteBuf的可讀字節數(換句話說就是這個新返回的緩衝區不能再進行寫入)
- slice(int index, int length):返回index開始,length長度的當前ByteBuf的子緩衝區,返回後的ByteBuf與原ByteBuf內容共享,讀寫索引獨立維護,maxCapacity是length(換句話說就是這個新返回的緩衝區不能再進行寫入)
轉換成標準的ByteBuffer
- ByteBuffer nioBuffer():將當前ByteBuf可讀的緩衝區轉換成ByteBuffer,二者共享同一個緩衝區內容引用,對ByteBuffer的讀寫操做並不會修改原ByteBuf的讀寫索引。返回後的ByteBuffer沒法感知ByteBuf的動態擴展。
- ByteBuffer nioBuffer(int index, int length):從ByteBuf的index位置開始長度爲length的緩衝區轉換成ByteBuffer,二者共享同一個緩衝區內容引用,對ByteBuffer的讀寫操做並不會修改原ByteBuf的讀寫索引。返回後的ByteBuffer沒法感知ByteBuf的動態擴展。
隨機讀寫
主要經過set和get開頭的方法,這兩個方法能夠指定索引位置。框架
ByteBuf源碼
從內存分配的角度來看,ByteBuf主要分爲如下兩類:性能
- 堆內存(HeapByteBuf)字節緩衝區:內存分配和回收速度快,能夠被JVM自動回收;缺點是若是Socket進行I/O讀寫,須要進行一次內存複製,將堆內存對應的緩衝區複製到內核Channel中,性能會有所降低
- 直接內存(DirectByteBuf)字節緩衝區:堆外內存直接分配,相比於堆內存,分配和回收速度比較慢,可是在Socket Channel中進行讀寫比較快(少一次內存複製)
ByteBuf的最佳時間是在I/O通訊線程的讀寫緩衝區使用DirectByteBuf,後端業務消息的編解碼模塊使用HeapByteBuf。
從內存回收的角度進行分類:
- 基於對象池的ByteBuf:本身維護了一個內存池,能夠重複利用ByteBuf對象,提高內存使用率,下降GC頻率
- 普通的ByteBuf
AbstractByteBuf
AbstractByteBuf繼承ByteBuf,ByteBuf中的一些公共屬性和方法會在AbstractByteBuf中實現。
主要變量
- ResourceLeakDetector<ByteBuf> leakDetector對象:被定義爲static,全部的ByteBuf實例共享一個ResourceLeakDetector<ByteBuf> leakDetector對象。ResourceLeakDetector主要用來檢測對象是否泄漏。
- 索引設置:讀寫索引、重置讀寫索引、最大容量
讀操做
讀操做的公共功能由父類實現,差別化由具體的子類實現。
選取readBytes(byte[] dst, int dstIndex, int length)分析:
- 首先對緩衝區可讀空間進行校驗:若是讀取的長度(length) < 0,會拋出IllegalArgumentException異常;若是可讀的字節數小於須要讀取的長度(length),會拋出IndexOutOfBoundsException異常
- 校驗經過以後,調用getBytes方法從當前的讀索引開始進行讀取(這一塊就須要由真正的子類來各自實現),複製length個字節到目標byte數組,數組開始的位置是dstIndex
- 讀取成功後,對讀索引進行遞增,增長的長度爲length
寫操做
寫操做的公共功能由父類實現,差別化由具體的子類實現。
選取writeBytes(byte[] src, int srcIndex, int length)分析:
- 首先對緩衝區的可寫空間進行校驗:若是要寫入的長度(length) < 0,會拋出IllegalArgumentException異常;若是要寫入的長度小於緩衝區可寫入的字節數,代表可寫;若是要寫入的長度 > 最大容量 - writeIndex,會拋出IndexOutOfBoundsException;不然進行擴容操做(擴容操做的原理前面已經講過)。
操做索引
與索引相關的操做主要涉及設置讀寫索引、mark、和reset等。
選取readerIndex(int readerIndex)進行分析:
- 首先對索引合法性進行判斷:若是readerIndex小於0或者readerIndex > writeIndex,則拋出IndexOutOfBoundsException異常
- 校驗經過以後,將讀索引設置爲readerIndex
重用緩衝區
選取discardReadBytes()進行分析:
- 若是readIndex等於0,直接返回
- 若是readIndex和writeIndex不相等,首先調用setBytes(int index, ByteBuf src, int srcIndex, int length)方法進行字節數組的複製,
而後從新設置markReadIndex、markWriteIndex、readIndex和writeIndex
- 若是readIndex等於writeIndex,調整markReadIndex和markWriteIndex,不進行字節數組複製,設置readIndex=writeIndex=0
skipBytes
- 校驗跳過的字節長度:若是跳過的字節長度小於0,則拋出IllegalArgumentException異常,若是跳過的字節數大於可讀取的字節數,則拋出IndexOutOfBoundsException異常
- 校驗經過以後,readIndex增長跳過的字節長度
AbstractReferenceCountedByteBuf
該類主要是對引用進行計數,相似於JVM內存回收的對象引用計數器,用於跟蹤對象的分配和銷燬,作自動內存回收。
成員變量
- AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater對象:經過原子的方式對成員變量進行更新操做,實現線程安全,消除鎖。
- volatile int refCnt:用於跟蹤對象的引用次數,使用volatile是爲了解決多線程併發訪問的可見性問題。
對象引用計數器
每調用retain()方法一次,引用計數器就會加1,但加完以後會對數據進行校驗,具體的校驗內容以下:
若是加1以前的引用次數小於等於0或者原來的引用次數 + 增長的次數 < 原來的引用次數,則須要還原此次引用計數器增長操做,而且拋出IllegalReferenceCountException異常
UnpooledHeapByteBuf
UnpooledHeapByteBuf是基於堆內存分配的字節緩衝區,每次I/O讀寫都會建立一個新的UnpooledHeapByteBuf。
成員變量
- ByteBufAllocator alloc:用於UnpooledHeapByteBuf的內存分配
- byte[] array:緩衝區數組,此處也可用ByteBuffer,可是用byte數組的緣由是提高性能和更加便捷的進行位操做
- ByteBuffer tmpNioBuf:用於實現Netty的ByteBuf到JDK NIO ByteBuffer的轉換
動態擴展緩衝區
- 校驗新容量:若是新容量小於0或者新容量大於最大容量,拋出IllegalArgumentException異常,不然校驗經過
- 若是新容量大於舊容量,使用new byte[newCapacity]建立新的緩衝數組,而後經過System.arraycopy進行復制,將舊的緩衝區內容拷貝到新的緩衝區中,最後在ByteBuf中替換舊的數組,而且將原來的ByteBuffer tmpNioBuf置爲空
- 若是新容量小於舊容量,使用new byte[newCapacity]建立新的緩衝數組,若是讀索引小於新容量(若是寫索引大於新容量,將寫索引直接置爲新容量),而後經過System.arraycopy將當前可讀的緩衝區內容複製到新的byte數組,若是讀索引大於新容量,說明沒有能夠拷貝的緩衝區,直接將讀寫索引置爲新容量,而且使用新的byte數組替換原來的字節數組
字節數組複製
setBytes(int index, byte[] src, int srcIndex, int length)
- 首先是合法性校驗,先是校驗index,length,若是這兩個值有小於0,或者相加小於0,或者兩個相加大於ByteBuf的容量,則拋出IndexOutOfBoundsException異常,接着校驗被複制的數組的長度和索引問題(srcIndex、length),若是srcIndex、length小於0,或者兩個相加小於0,或者兩個相加超過了src字節數組的容量,也拋出IndexOutOfBoundsException異常
- 校驗經過以後,使用System.arraycopy方法進行字節數組的拷貝
ByteBuf以get和set開頭讀寫緩衝區的方法不會修改讀寫索引
轉換成JDK ByteBuffer
因爲UnpooledHeapByteBuf緩衝區採用了byte數組實現,一樣的ByteBuffer底層也是用了byte數組實現,同時ByteBuffer還提供了wrap方法,
直接將字節數組轉換成ByteBuffer,最後調用slice方法。因爲每次調用都會建立一個新的ByteBuffer,所以起不到重用緩衝區內容的效果。
子類實現相關的方法
- hasArray():是否支持數組,判斷緩衝區的實現是否基於字節數組
- 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相互獨立。
- 首先校驗索引和長度的合法性
- 校驗經過以後,調用PooledByteBufAllocator分配一個新的ByteBuf,最終會調用PooledByteBufAllocator中的newDirectBuffer(int initialCapacity, int maxCapacity)方法進行內存的分配
- 在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。列舉三個:
- ByteBuf encodeString(ByteBufAllocator alloc, CharBuffer src, Charset charset):對須要編碼的字符串src按照指定的字符集charset進行編碼,利用指定的ByteBufAllocator生成一個ByteBuf
- decodeString(ByteBuf src, int readerIndex, int len, Charset charset):從指定索引readIndex開始日後len個字節長度,對ByteBuf對象src按照指定的字符集charset進行解碼
- hexDump(ByteBuf buffer):將ByteBuf對象的參數內容以十六進制的格式輸出