netty系列之:不用懷疑,netty中的ByteBuf就是比JAVA中的好用

flydean發表於2022-02-16

簡介

netty作為一個優秀的的NIO框架,被廣泛應用於各種伺服器和框架中。同樣是NIO,netty所依賴的JDK在1.4版本中早就提供nio的包,既然JDK已經有了nio的包,為什麼netty還要再寫一個呢?

不是因為JDK不優秀,而是因為netty的要求有點高。

ByteBuf和ByteBuffer的可擴充套件性

在講解netty中的ByteBuf如何優秀之前,我們先來看一下netty中的ByteBuf和jdk中的ByteBuffer有什麼關係。

事實上,沒啥關係,只是名字長的有點像而已。

jdk中的ByteBuffer,全稱是java.nio.ByteBuffer,屬於JAVA nio包中的一個基礎類。它的定義如下:

public abstract class ByteBuffer
    extends Buffer
    implements Comparable<ByteBuffer>

而netty中的ByteBuf,全稱是io.netty.buffer,屬於netty nio包中的一個基礎類。它的定義如下:

public abstract class ByteBuf 
implements ReferenceCounted, Comparable<ByteBuf>

兩者的定義都很類似,兩者都是抽象類,都需要具體的類來實現他們。

但是,當你嘗試去建立一個類來繼承JDK的ByteBuffer,則會發現繼承不了,為什麼命名一個abstract的類會繼承不了呢?

仔細研究會發現,在ByteBuffer中,定義了下面兩個沒有顯示標記其作用域訪問的方法:

    abstract byte _get(int i);                          // package-private
    abstract void _put(int i, byte b);                  // package-private

根據JDK的定義,沒有顯示標記作用域的方法,預設其訪問訪問是package,當這兩個方法又都是abstract的,所以只有同一個package的類才能繼承JDK的ByteBuffer。

當然,JDK本身有5個ByteBuffer的實現,他們分別是DirectByteBuffer,DirectByteBufferR,HeapByteBuffer,HeapByteBufferR和MappedByteBuffer。

但是JDK限制了使用者自定義類對ByteBuffer的擴充套件。雖然這樣可以保證ByteBuffer類在使用上的安全性,但是同時也現在了使用者需求的多樣性。

既然JDK的ByteBuffer不能擴充套件,那麼很自然的netty中的ByteBuf跟它就沒有任何關係了。

netty中的ByteBuff是參考了JDK的ByteBuffer,並且做了很多有意義的提升,讓ByteBuff更加好用。

和JDK的ByteBuffer相比,netty中的ByteBuf並沒有擴充套件的限制,你可以自由的對其進行擴充套件和修改。

不同的使用方法

JDK中的ByteBuffer和netty中的ByteBuff都提供了對各種型別資料的讀寫功能。

但是相對於netty中的ByteBuff, JDK中的ByteBuffer使用其來比較複雜,因為它定義了4個值來描述ByteBuffer中的資料和使用情況,這四個值分別是:mark,position,limit和capacity。

  • capacity是它包含的元素數。 capacity永遠不會為負且永遠不會改變。
  • limit是不應讀取或寫入的第一個元素的索引。 limit永遠不會為負,也永遠不會大於其容量。
  • position是要讀取或寫入的下一個元素的索引。 position永遠不會為負,也永遠不會大於其限制。
  • mark是呼叫 reset 方法時其位置將重置到的索引。 mark並不一定有值,但當它有值的時候,它永遠不會是負的,也永遠不會大於position。

上面4個值的關係是:

0 <= mark <= position <= limit <= capacity

然後JDK還提供了3個處理上面4個標記的方法:

  • clear : 將 limit設定為capacity,並將position設定為0,表示可以寫入。
  • flip : 將 limit設定為當前位置,並將position設定為0.表示可以讀取。
  • rewind : limit不變,將position設定為0,表示重新讀取。

是不是頭很大?

太多的變數,太多的方法,雖然現在你可能記得,但是過一段時間就會忘記到底該怎麼正確使用JDK的ByteBuffer了。

和JDK不同的是,netty中的ByteBuff,只有兩個index,分別是readerIndex 和 writerIndex 。

除了index之外,ByteBuff還提供了更加豐富的讀寫API,方便我們使用。

效能上的不同

對於JDK的java.nio.ByteBuffer來說,當我們為其分配空間的時候,buffer中會被使用0來填充。雖然這些0可能會馬上被真正有意義的值來進行替換。但是不可否認,填充的過程消耗了CPU和記憶體。

另外JDK的java.nio.ByteBuffer是依賴於垃圾回收器來進行回收的,但是我們之前講過了,ByteBuffer有兩種內型,一種是HeapBuffer,這種型別是由JVM進行管理的,用垃圾回收器來進行回收是沒有問題的。

但是問題在於還有一類ByteBuffer是DirectByteBuffer,這種Buffer是直接分配在外部記憶體上的,並不是由JVM來進行管理.通常來說DirectBuffer可能會存在較長的時間,如果短時間分配大量的短生命週期的DirectBuffer,會導致這些Buffer來不及回收,從而導致OutOfMemoryError.

另外使用API來回收DirectBuffer的速度也不是那麼快。

相對而言,netty中的ByteBuf使用的是自己管理的引用計數。當ByteBuf的引用計數歸零的時候,底層的記憶體空間就會被釋放,或者返回到記憶體池中。

我們看一下netty中direct ByteBuff的使用:

ByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;
ByteBuf buf = alloc.directBuffer(1024);
...
buf.release(); // 回收directBuffer

當然,netty這種自己管理引用計數也有一些缺點,可能會在pooled buffer被垃圾回收之後,pool中的buffer才返回,從而導致記憶體洩露。

還好,netty提供了4種檢測引用計數記憶體洩露的方法,分別是:

  • DISABLED---禁用洩露檢測
  • SIMPLE --預設的檢測方式,佔用1% 的buff。
  • ADVANCED - 也是1%的buff進行檢測,不過這個選項會展示更多的洩露資訊。
  • PARANOID - 檢測所有的buff。

具體的檢測選項如下:

java -Dio.netty.leakDetection.level=advanced ...

總結

以上就是netty中優秀的ByteBuff和JDK中的對比。還不趕緊用起來。

本文已收錄於 http://www.flydean.com/45-netty-bytebuf-bytebuffer/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!

相關文章