netty之DirectByteBuf 和 HeapByteBuf浅谈

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了netty之DirectByteBuf 和 HeapByteBuf浅谈相关的知识,希望对你有一定的参考价值。

参考技术A ByteBuf是netty中数据传输的容器,用来替代NIO中的ByteBuffer。其主要还是一个byte数组,以及包含了一些数据的操作方法。通过两个指针readerIndex和writerIndex来读写分离,更好的处理数据。其结构大致如下图所示。

而从内存分配的角度来讲,ByteBuf又分为两种,DirectByteBuf和HeapByteBuf。简而言之就是一种是分配在Direct Memory上的,一种是分配在Heap Memory上的。

直接内存的好处就是利用的是native库,读写快速。但是它不在虚拟机的管理范围之内,这部分内存只有在进行full gc时才会进行回收,而他的容量如果没有明确限制,随着数据的不断读写势必造成内存中可利用的空间不断变小。所以netty做了引用计数机制来处理direct memory上的数据。其实对heap上的也做了引用计数。

其主要用了这两个变量来处理引用计数问题。计数器基于 AtomicIntegerFieldUpdater,为什么不直接用AtomicInteger?因为ByteBuf对象很多,如果都把int包一层AtomicInteger花销较大,而AtomicIntegerFieldUpdater只需要一个全局的静态变量。

这两种ByteBuf有各自的特点,用于针对不同的场景。
HeapByteBuf :特点是内存的分配和回收速度快。可以被jvm自动回收。缺点就是在进行socket的I/O读写时,需要将堆内存的缓冲区拷贝到内核中,有一定拷贝的代价。
DirectByteBuf:特点是分配在堆内存外,相比于堆内存分配速度会慢一点,并需要手动管理其引用计数,清理不使用的内存。但是其直接用native方法,不需要拷贝。

ByteBuf从内存回收策略上来说也分为两种pool和unpool。其中poolByteBuf主要是建立了一块内存池来管理ByteBuf。其主要为一块poolArena,其中维护这多个poolChunk。其变量大致如下。

如何设置Netty的接收Buffer为堆内存模式

如何设置Netty的接收Buffer为堆内存模式

Netty为了提升报文的读写性能,默认会采用“零拷贝”模式,即消息读取时使用非堆的DirectBuffer来减少ByteBuffer的内存拷贝,如下图所示:



如果需要修改接收Buffer的类型,例如从DirectByteBuf修改为HeapByteBuf,首先需要在初始化Channel的时候对接收缓冲区进行设置,客户端代码示例如下:


 b.group(group)

.channel(NioSocketChannel.class)

 .option(ChannelOption.TCP_NODELAY, true)

 .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)

 .option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)


如果是服务端,则需要在链路创建之后,初始化时对Channel的SocketChannelConfig的Allocator属性进行设置,代码如下:


.childHandler(new ChannelInitializer<SocketChannel>() {          

 @Override

  public void initChannel(SocketChannel ch) throws Exception {

  ch.config().setAllocator(UnpooledByteBufAllocator.DEFAULT);


无论是通过ChannelOption.RCVBUF_ALLOCATOR还是Allocator都只能设置Allocator的类型,无法直接设置ByteBufAllocator分配的ByteBuf类型。下面我们接着分析消息读取时的ByteBuf分配机制。消息读取时,调用的是NioByteUnsafe的read方法,代码如下:



如何设置Netty的接收Buffer为堆内存模式


首先从SocketChannelConfig中获取ByteBufAllocator,在这里就是UnpooledByteBufAllocator,然后调用它的ioBuffer方法分配内存,它的具体实现如下:


 @Override

    public ByteBuf ioBuffer() {

        if (PlatformDependent.hasUnsafe()) {

            return directBuffer(0);

        }

        return heapBuffer(0);

    }


如果非Unsafe模式,则会使用堆内存heapBuffer,接着看hasUnsafe方法实现,它最终会调用如下方法:


如何设置Netty的接收Buffer为堆内存模式


设置io.netty.noUnsafe属性为true,则默认会使用Heap堆内存创建ByteBuf,下面我们在启动时设置-Dio.netty.noUnsafe为true进行测试




设置完成之后,使用Echo程序进行测试,测试结果如下:


总结

利用ch.config().setAllocator或Bootstrap.option(ChannelOption.ALLOCATOR, ByteBufAllocator),结合-Dio.netty.noUnsafe,可以灵活的在如下四种ByteBuf之间进行切换:

  • UnpooledHeapByteBuf

  • PooledHeapByteBuf

  • UnpooledDirectByteBuf

  • PooledDirectByteBuf

以上是关于netty之DirectByteBuf 和 HeapByteBuf浅谈的主要内容,如果未能解决你的问题,请参考以下文章

如何设置Netty的接收Buffer为堆内存模式

95-38-055-Buffer-UnpooledDirectByteBuf

netty系列之:NIO和netty详解

netty系列之:channel和channelGroup

netty系列之:在netty中实现线程和CPU绑定

#yyds干货盘点#netty系列之:Bootstrap,ServerBootstrap和netty中的实现