共计 2990 个字符,预计需要花费 8 分钟才能阅读完成。
简介
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/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!