網絡數據的基本單位總是字節。Java NIO 提供了 ByteBuffer 作為它的字節容器,但是這個類使用起來過於復雜,而且也有些繁瑣。Netty 的 ByteBuffer 替代品是 ByteBuf,一個強大的實現,既解決了 JDK API 的局限性,又為網絡應用程序的開發者提供了更好的 API
Netty系列索引:
ByteBuf優勢
- 它可以被用戶自定義的緩沖區類型擴展
- 通過內置的復合緩沖區類型實現了透明的零拷貝
- 容量可以按需增長
- 在讀和寫這兩種模式之間切換不需要調用 ByteBuffer 的 flip()方法
- 讀和寫使用了不同的索引
- 支持方法的鏈式調用
- 支持引用計數
- 支持池化
ByteBuf實現原理
如圖ByteBuf通維護了兩個不同的索引:一個用於讀取,一個用於寫入。
當你從 ByteBuf 讀取時,它的 readerIndex 將會被遞增已經被讀取的字節數。同樣地,當你寫入 ByteBuf 時,它的writerIndex 也會被遞增
當調用readBytes時,readIndex會相應移動length位,如果readIndex移動后大於writeIndex則會拋異常。
當調用writeBytes時,writeIndex會相應移動length位,且通過ensureWritable方法實現自動擴容
其他常用API
getBytes | 獲取可讀字節數組 |
setBytes | 寫入字節 |
discardReadBytes | 廢棄已讀字節 |
mark | 標記index |
reset | 將index重置到之前標記的位置(配合mark使用) |
isReadable | 如果至少有一個字節可供讀取,則返回 true |
isWritable | 如果至少有一個字節可被寫入,則返回 true |
readableBytes | 返回可被讀取的字節數 |
writableBytes | 返回可被寫入的字節數 |
capacity | 返回 ByteBuf 可容納的字節數。在此之后,它會嘗試再次擴展直到達到 maxCapacity() |
maxCapacity | 返回 ByteBuf 可以容納的最大字節數 |
hasArray | 如果 ByteBuf 由一個字節數組支撐,則返回 true |
array | 如果 ByteBuf 由一個字節數組支撐則返回該數組;否則,它將拋出一個UnsupportedOperationException 異常 |
ByteBuf緩沖分類
1、Heap buffer(堆緩沖區):
就是將數據存在JVM堆空間中,在沒有被池化的情況可以快速分配和釋放。
優點:由於數據是存儲在JVM堆中,因此可以快速的創建與快速的釋放,並且它提供了直接訪問內部字節數組的方法。
缺點:每次讀寫數據時,都需要先將數據復制到直接緩沖區中再進行網路傳輸。
2、Direct buffer(直接緩沖區):
直接緩沖區,在堆外直接分配內存空間,直接緩沖區並不會占用堆的容量空間,因為它是由操作系統在本地內存進行的數據分配。
優點:在使用Socket進行數據傳遞時,性能非常好,因為數據直接位於操作系統的本地內存中,所以不需要從JVM將數據復制到直接緩沖區中 。
缺點:因為Direct Buffer是直接在操作系統內存中的,所以內存空間的分配與釋放要比堆空間更加復雜,而且速度要慢一些。
注意:
如果你的數據包含在一個在堆上的分配的緩沖區中,那么事實上,在通過套接字發送他之前,jvm將會在內部把你的緩沖區復制到一個直接緩沖區中;這樣分配釋放就比較浪費資源;
建議:
直接緩沖區並不支持通過字節數組的方式來訪問數據。對於后端業務的消息編解碼來說,推薦使用HeapByteBuf;對於I/O通信線程在讀寫緩沖區時,推薦使用DirectByteBuf;
3、Composite Buffer 復合緩沖區:
可以擁有以上兩種的緩沖區,通過一種聚合視圖來操作底層持有的多種類型Buffer。這種緩沖,jdk nio是沒有這種特性的。
ByteBuf主要實現類
pooled:池化,重用ByteBuf對象
Direct:直接內存,內部通過ByteBuffer實現,典型裝飾模式
Heap:堆內存,內部持有byte數組
(1)UnpooledDirectByteBuf:
在堆外進行內存分配的非內存池ByteBuf,內部持有ByteBuffer對象,相關操作委托給ByteBuffer實現。
(2)UnpooledHeapByteBuf:
基於堆內存分配非內存池ByteBuf,即內部持有byte數組。
(3)UnpooledUnsafeDirectByteBuf:
和另外一個類UnpooledDirectByteBuf差不多相同,區別在於UnpooledUnsafeDirectByteBuf內部使用基於PlatformDependent相關操作實現ByteBuf,依賴平台。
(4)ReadOnlyByteBufferBuf:
只讀ByteBuf,內部持有ByteBuffer對象,相關操作委托給ByteBuffer實現,該ByteBuf限內部使用;
(5)FixedCompositeByteBuf:
用於將多個ByteBuf組合在一起,形成一個虛擬的只讀ByteBuf對象,不允許寫入和動態擴展。內部使用Object[]將多個ByteBuf組合在一起,一旦FixedCompositeByteBuf對象構建完成,則不會被更改。
(6)CompositeByteBuf:
用於將多個ByteBuf組合在一起,形成一個虛擬的ByteBuf對象,支持讀寫和動態擴展。內部使用List組合多個ByteBuf。一般使用使用ByteBufAllocator的compositeBuffer()方法,Unpooled的工廠方法compositeBuffer()或wrappedBuffer(ByteBuf... buffers)創建CompositeByteBuf對象。
(7)PooledByteBuf:
基於內存池的ByteBuf,主要為了重用ByteBuf對象,提升內存的使用效率;適用於高負載,高並發的應用中。主要有PooledDirectByteBuf,PooledHeapByteBuf,PooledUnsafeDirectByteBuf三個子類,PooledDirectByteBuf是在堆外進行內存分配的內存池ByteBuf,PooledHeapByteBuf是基於堆內存分配內存池ByteBuf,PooledUnsafeDirectByteBuf也是在堆外進行內存分配的內存池ByteBuf,區別在於PooledUnsafeDirectByteBuf內部使用基於PlatformDependent相關操作實現ByteBuf,具有平台相關性。
ByteBufHolder
利用組合的方式對ByteBuf進行擴展。實際應用中我們經常發現,除了實際的數據負載之外,我們還需要存儲各種屬性值。HTTP 響應便是一個很好的例子,除了表示為字節的內容,還包括狀態碼、cookie 等。為了處理這種常見的用例,Netty 提供了 ByteBufHolder。ByteBufHolder 也為 Netty 的高級特性提供了支持,如緩沖區池化,其中可以從池中借用 ByteBuf,並且在需要時自動釋放。
ByteBufAllocator
為了降低分配和釋放內存的開銷,Netty 通過 ByteBufAllocator 實現了(ByteBuf 的)池化,它可以用來分配我們所描述過的任意類型的 ByteBuf 實例。Netty提供了兩種ByteBufAllocator的實現:PooledByteBufAllocator和UnpooledByteBufAllocator。前者池化了ByteBuf的實例以提高性能並最大限度地減少內存碎片。
ReferenceCounted
類似GC引用計數法,Netty中ByteBuf和ByteBufHolder都實現了該接口,其主要通過方法retain(release)來增加(減少)資源被引用的次數,當引用為0時,代表該資源可以被釋放。