簡介
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/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!