写点什么

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

作者:程序那些事
  • 2022 年 1 月 17 日
  • 本文字数:2286 字

    阅读完需:约 8 分钟

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

简介

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/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 刚刚阅读数: 2
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:不用怀疑,netty中的ByteBuf就是比JAVA中的好用