ByteBuf和Channel和Pipeline

时间:2019-08-28
本文章向大家介绍ByteBuf和Channel和Pipeline,主要包括ByteBuf和Channel和Pipeline使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

BytetBuf

ByteBuf就是JDK nio中Buffer的新轮子
buffer 的主要目的进行流量整形,把突发的大数量较小规模的 I/O 整理成平稳的小数量较大规模的 I/O,以减少响应次数

ByteBuffer:

  1. 长度固定,一旦分配完成,它的容量不能动态扩展和收缩,当需要编码的POJO对象大于ByteBuffer的容量时,会发生索引越界异常;
  2. ByteBuffer只有一个标识位控的指针position,读写的时候需要手工调用flip()和rewind()等,使用者必须小心谨慎地处理这些API,否则很容易导致程序处理失败;
  3. ByteBuffer的API功能有限,一些高级和实用的特性它不支持,需要使用者自己编程实现。
  1. 需要的话,可以自定义buffer类型;
  2. 通过组合buffer类型,可实现透明的zero-copy;
  3. 提供动态的buffer类型,如StringBuffer一样,容量是按需扩展;
    • 如果c<t,则n从阈值t(4MB)开始,以每次增加2倍的方式扩容,直到双倍后的大小小于c;
    • 如果c>t,则n=c/t*t+t
  4. 无需调用flip()方法;方法反转(讲Buffer从读模式变成写模式)
  5. 常常比ByteBuffer(JDK的)快
  6. 使用了读写两个指针,分别记录读写的位置,复杂操作更简单

堆内存和直接内存

NIO的Buffer提供了一个可以不经过JVM内存直接访问系统物理内存的类——DirectBuffer。 DirectBuffer类继承自ByteBuffer,但和普通的ByteBuffer不同,普通的ByteBuffer仍在JVM堆上分配内存,其最大内存受到最大堆内存的限制;而DirectBuffer直接分配在物理内存中,并不占用堆空间,其可申请的最大内存受操作系统限制。

直接内存的读写操作比普通Buffer快,但它的创建、销毁比普通Buffer慢(猜测原因是DirectBuffer需向OS申请内存涉及到用户态内核态切换,而后者则直接从堆内存划内存即可)。

因此直接内存使用于需要大内存空间且频繁访问的场合,不适用于频繁申请释放内存的场合。

Note:DirectBuffer并没有真正向OS申请分配内存,其最终还是通过调用Unsafe的allocateMemory()来进行内存分配。不过JVM对Direct Memory可申请的大小也有限制,可用-XX:MaxDirectMemorySize=1M设置,这部分内存不受JVM垃圾回收管理。

最佳实践:在I/O通信线程的读写缓冲区使用DirectByteBuf,后端业务消息的编解码模块使用HeapByteBuf。

从内存回收角度看,ByteBuf分2类:

基于对象池的ByteBuf和普通ByteBuf。

两者的主要区别就是基于对象池的ByteBuf可以重用ByteBuf对象,它自己维护一个内存池,可以循环利用创建的ByteBuf,提高内存使用效率,降低由于高负载导致的频繁GC。测试表明使用内存池后的Netty在高负载、大并发的冲击下内存和GC更加平稳。

内存池化

池化的简单实现思路,是基于JVM堆内存之上,构建更高一层内存池,通过调用内存池allocate方法获取内存空间,调用release方法将内存区域归还内存池。内存池面临的首要问题是碎片回收,内存池在频繁申请和释放空间后,还能有尽可能连续的内存空间用于大块内存空间的分配。基于这个需求,有两种算法用于优化这一块的内存分配:伙伴系统和slab系统。

netty4相对于netty3的一大改进就是引入了内存池化技术,用以解决高速网络通信过程中,netty造成的应用内存锯齿状消费和大量gc的问题。

Netty中的零拷贝

其实netty中并没有实现真正的零拷贝,netty中的零拷贝更多的应该理解为少拷贝或者说复用(reuse),操作系统的零拷贝是避免了CPU将数据从一个内存区域拷贝到另一个内存区域,而netty中的数据操作全部是在用户态。当真正要通过netty将数据发送到网络时,仍然需要将数据从用户态拷贝到内核态,此时就无法做到真正的零拷贝了。
Netty的零拷贝(或者说ByteBuf的复用)主要体现在以下几个方面:

  • DirectByteBuf通过直接在堆外分配内存的方式,避免了数据从堆内拷贝到堆外的过程
  • 通过组合ByteBuf类:即CompositeByteBuf,将多个ByteBuf合并为一个逻辑上的ByteBuf, 而不需要进行数据拷贝
  • 通过各种包装方法, 将 byte[]、ByteBuf、ByteBuffer等包装成一个ByteBuf对象,而不需要进行数据的拷贝
  • 通过slice方法, 将一个ByteBuf分解为多个共享同一个存储区域的ByteBuf, 避免了内存的拷贝,这在需要进行拆包操作时非常管用
  • 通过FileRegion包装的FileChannel.tranferTo方法进行文件传输时, 可以直接将文件缓冲区的数据发送到目标Channel, 减少了通过循环write方式导致的内存拷贝。但是这种方式是需要得到操作系统的零拷贝的支持的,如果netty所运行的操作系统不支持零拷贝的特性,则netty仍然无法做到零拷贝

参考

https://www.jianshu.com/p/f7c668cd05cd

https://www.itcodemonkey.com/article/4655.html

https://blog.csdn.net/qq157538651/article/details/93537187

https://www.cnblogs.com/z-sm/p/6235157.html

https://sq.163yun.com/blog/article/213832853624152064

https://www.cnblogs.com/xys1228/p/6088805.html

http://www.52im.net/thread-99-1-1.html

原文地址:https://www.cnblogs.com/colin-xun/p/11422606.html