Netty之ByteBuf应用详解

目录

目标

概述

实战

创建直接内存的ByteBuf和堆内存的ByteBuf

创建池化的ByteBuf和非池化的ByteBuf

扩容ByteBuf

ByteBuf写出方法

ByteBuf读入方法

释放ByteBuf的内存

修改ByteBuf

对ByteBuf进行切片(逻辑上的切分)

复制ByteBuf(物理上的)

组合多个ByteBuf


目标

  • 掌握ByteBuf的常用方法。
  • 了解池化的ByteBuf和非池化的ByteBuf的区别。
  • 了解直接内存的ByteBuf和堆内存的ByteBuf的区别。
  • 掌握对ByteBuf的内存释放方法。

概述

池化的ByteBuf和非池化的ByteBuf的区别

类比数据库连接池,池化的ByteBuf可以被重用,对高并发有很好的节约内存的效果。4.1以前的版本默认创建非池化ByteBuf,4.1以后的版本默认创建池化的ByteBuf,但是Android默认创建非池化的ByteBuf。

直接内存的ByteBuf和堆内存的ByteBuf的区别

直接内存的ByteBuf创建和销毁代价大,但是读写性能高,因为少了一次内存复制。减少了垃圾回收机制的压力。

ByteBuf扩容规律

ByteBuf可以指定初始容量也可不指定。不指定初始容量,则容量默认256个字节大小。数据超过容量以后ByteBuf会自动扩容。扩容规则如下:

  • 扩容小于等于512,扩容为16的倍数大小。
  • 扩容大于512,扩容为2的n次方大小。

推荐创建ByteBuf的方法

在Pipeline中创建ByteBuf时推荐使用ChannelHandlerContext,如:

        pipeline.addLast("InboundHandler2", new ChannelInboundHandlerAdapter() {
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                ByteBuf buffer = ctx.alloc().buffer(10);
            }
        });

实战

创建直接内存的ByteBuf和堆内存的ByteBuf

    /**
     * 创建基于直接内存和堆内存的ByteBuf
     * 直接内存创建和销毁代价大,但是读写速度快。
     */
    public void directAndHeap(){
        //直接内存
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer();
        System.out.println(byteBuf.getClass());
        //堆内存
        ByteBuf byteBuf1 = ByteBufAllocator.DEFAULT.heapBuffer();
        System.out.println(byteBuf1.getClass());
    }

输出结果 


创建池化的ByteBuf和非池化的ByteBuf

第一步:以idea为例,需要做一下配置。

 第二步

 第三步:如图所示,需要将VM options设置为-Dio.netty.allocator.type=unpooled

 第四步:创建ByteBuf并输出ByteBuf类名,此时发现ByteBuf变成了非池化类型。需要注意,ByteBuf默认创建池化类型。


扩容ByteBuf

    /**
     * ByteBuf扩容案例
     * ByteBuf可以指定初始容量也可不指定。不指定初始容量,则容量默认256个字节大小。
     * 数据超过容量以后ByteBuf会自动扩容。扩容规则如下:
     * 扩容小于等于512,扩容为16的倍数大小。
     * 扩容大于512,扩容为2的n次方大小。
     */
    public void addCapacity(){
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
        System.out.println(buffer);
        StringBuffer sb = new StringBuffer();
        //添加257个字节大小的字符串。
        for(int i=0;i<257;i++){
            sb.append("a");
        }
        buffer.writeBytes(sb.toString().getBytes());
        System.out.println(buffer);
    }
    /**
     * ByteBuf扩容案例
     * ByteBuf可以指定初始容量也可不指定。不指定初始容量,则容量默认256个字节大小。
     * 数据超过容量以后ByteBuf会自动扩容。扩容规则如下:
     * 扩容小于等于512,扩容为16的倍数大小。
     * 扩容大于512,扩容为2的n次方大小。
     */
    public void addCapacity2(){
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
        System.out.println(buffer);
        StringBuffer sb = new StringBuffer();
        //添加513个字节大小的字符串。
        for(int i=0;i<513;i++){
            sb.append("a");
        }
        buffer.writeBytes(sb.toString().getBytes());
        System.out.println(buffer);
    }

ByteBuf写出方法

    public void writeTest() {
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.heapBuffer();
        //写入byte[]
        byte[] bytes =new byte[]{'a', 'b', 'c'};
        byteBuf.writeBytes(bytes);
        //写入整数
        byteBuf.writeInt(1);
        //写入long
        byteBuf.writeLong(1L);
        //写入字符串
        byteBuf.writeCharSequence("Hello World!",Charset.forName("UTF-8"));
        //写入StringBuffer
        StringBuffer sb = new StringBuffer();
        byteBuf.writeCharSequence(sb,Charset.forName("UTF-8"));
        //写入java.nio.ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(8);
        byteBuf.writeBytes(buffer);
    }

ByteBuf读入方法

    /**
     * 循环读取ByteBuf,读取过程中移动读指针。
     */
    public void circulateReadTest(){
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.heapBuffer();
        byteBuf.writeCharSequence("Hello World!",Charset.forName("UTF-8"));
        //循环读取ByteBuf,观察读指针(ridx)的变化情况。
        System.out.println(byteBuf);
        for(int i=byteBuf.readerIndex();i<byteBuf.writerIndex();i++){
            System.out.print((char)byteBuf.readByte());
        }
        System.out.println();
        System.out.println(byteBuf);
    }
    /**
     * 循环读取ByteBuf,读取过程中不移动读指针。
     */
    public void circulateReadTest2(){
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.heapBuffer();
        byteBuf.writeCharSequence("Hello World!",Charset.forName("UTF-8"));
        System.out.println(byteBuf);
        System.out.println((char)byteBuf.readByte());
        System.out.println(byteBuf);
        byteBuf.markReaderIndex();
        System.out.println("标记读指针,读指针ridx="+byteBuf.readerIndex());
        System.out.println((char)byteBuf.readByte());
        System.out.println("读取一个byte,此时的读指针ridx="+byteBuf.readerIndex());
        byteBuf.resetReaderIndex();
        //查看读指针是否被重置。
        System.out.println("重置读指针,读指针ridx="+byteBuf.readerIndex());
    }

    /**
     * 循环读取ByteBuf,读取过程中不移动读指针。
     */
    public void readTest2(){
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.heapBuffer();
        byteBuf.writeCharSequence("Hello World!",Charset.forName("UTF-8"));
        System.out.println(byteBuf);
        for(int i=0;i<byteBuf.writerIndex();i++){
            System.out.print((char)byteBuf.getByte(i));
        }
        System.out.println();
        System.out.println(byteBuf);
    }

释放ByteBuf的内存

概述

  • 非池化的堆内存ByteBuf使用JVM内存,由垃圾回收机制回收内存。
  • 非池化的直接内存ByteBuf也可以被垃圾回收机制间接回收内存。
  • 池化的ByteBuf会使用更复杂的内存回收机制回收内存。

ByteBuf实现了io.netty.util.ReferenceCounted接口,采用了引用计数的方法来标记ByteBuf对象的使用情况。其中:

  • release()方法减1,如果计数为0,则ByteBuf被回收。
  • retain()方法加1。

ByteBuf调用release()需要特别注意

第一:并不是在每个Handler的最后调用release(),而是最后一个使用ByteBuf的Handler负责计数减1。因为ByteBuf可能在多个Handler中传递,一旦每个Handler都负责计数减1,则下一个Handler就可能因为ByteBuf的底层内存被回收而无法使用。

第二:虽然Pipeline自带头部和尾部Handler,且会回收ByteBuf的内存,但前提是ByteBuf必须要流转至头部或者尾部,内存才会被回收。比如Handler_1将ByteBuf传递给Handler_2,Handler_2将ByteBuf转成String传递给头部和尾部Handler,则ByteBuf所在内存仍然没有被回收。


源码跟进

尾部Handler对对象计数减1的源码

io.netty.channel.ChannelPipeline接口
io.netty.channel.DefaultChannelPipeline类
io.netty.channel.DefaultChannelPipeline.TailContext类的channelRead(ChannelHandlerContext ctx, Object msg)方法
再跟进到了onUnhandledInboundMessage(Object msg)方法,该方法的内部有ReferenceCountUtil.release(msg)负责计数减1
进入release(msg),发现对象必须要实现io.netty.util.ReferenceCounted接口,对象计数才会被减1

    public static boolean release(Object msg) {
        return msg instanceof ReferenceCounted ? ((ReferenceCounted)msg).release() : false;
    }

头部Handler对对象计数减1的源码

io.netty.channel.ChannelPipeline接口
io.netty.channel.DefaultChannelPipeline类
io.netty.channel.DefaultChannelPipeline.HeadContext类的write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)方法
再跟进到了io.netty.channel.AbstractChannel类的write(Object msg, ChannelPromise promise)方法,当没有出栈缓存时进入计数减1操作,同样地,进入release(msg)后发现对象必须要实现io.netty.util.ReferenceCounted接口,对象计数才会被减1

    public final void write(Object msg, ChannelPromise promise) {
            this.assertEventLoop();
            ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            if (outboundBuffer == null) {
                try {
                    ReferenceCountUtil.release(msg);
                } finally {
                    this.safeSetFailure(promise, this.newClosedChannelException(AbstractChannel.this.initialCloseCause, "write(Object, ChannelPromise)"));
                }

            } else {
                int size;
                try {
                    msg = AbstractChannel.this.filterOutboundMessage(msg);
                    size = AbstractChannel.this.pipeline.estimatorHandle().size(msg);
                    if (size < 0) {
                        size = 0;
                    }
                } catch (Throwable var15) {
                    try {
                        ReferenceCountUtil.release(msg);
                    } finally {
                        this.safeSetFailure(promise, var15);
                    }

                    return;
                }

                outboundBuffer.addMessage(msg, size, promise);
            }
        }

    public static boolean release(Object msg) {
        return msg instanceof ReferenceCounted ? ((ReferenceCounted)msg).release() : false;
    }

修改ByteBuf

    /**
     * 根据下标修改ByteBuf元素
     */
    public void updateTest(){
        //创建一个初始容量为10字节大小的ByteBuf
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer(10);
        byteBuf.writeBytes(new byte[]{'1','2','3','4','5','6','7','8','9','0'});
        //修改下标为0的元素为'2'
        byteBuf.setByte(0,'2');
        for(int i=0;i<byteBuf.writerIndex();i++){
            System.out.print((char)byteBuf.readByte());
        }
    }

对ByteBuf进行切片(逻辑上的切分)

    /**
     * 切分ByteBuf
     */
    public void sliceTest(){
        //创建一个初始容量为10字节大小的ByteBuf
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer(10);
        byteBuf.writeBytes(new byte[]{'1','2','3','4','5','6','7','8','9','0'});
        //从下标2开始切分,切取5个长度。
        ByteBuf sliceByteBuf = byteBuf.slice(2, 5);
        for(int i=0;i<sliceByteBuf.writerIndex();i++){
            System.out.print((char)sliceByteBuf.getByte(i));
        }
        System.out.println();
        //证明slice()方法是逻辑上的切分而非物理上的切分。
        sliceByteBuf.setByte(0,'a');
        for(int i=0;i<sliceByteBuf.writerIndex();i++){
            System.out.print((char)sliceByteBuf.getByte(i));
        }
        System.out.println();
        for(int i=0;i<byteBuf.writerIndex();i++){
            System.out.print((char)byteBuf.getByte(i));
        }
    }

特别注意

  • 切片后的ByteBuf不能扩容。
  • 如果原始的ByteBuf内存被释放,则切片后的ByteBuf不能使用。所以切片后的ByteBuf需要将计数加一。

推荐案例

    public void sliceTest(){
        //创建一个初始容量为10字节大小的ByteBuf
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer(10);
        byteBuf.writeBytes(new byte[]{'1','2','3','4','5','6','7','8','9','0'});
        //从下标2开始切分,切取5个长度。
        ByteBuf sliceByteBuf = byteBuf.slice(2, 5);
        //引用计数加一。
        sliceByteBuf.retain();
        //将原始的ByteBuf释放掉。
        byteBuf.release();
        //发现原来的ByteBuf还可以继续使用。
        for(int i=0;i<sliceByteBuf.writerIndex();i++){
            System.out.print((char)byteBuf.getByte(i));
        }
        System.out.println();
        //切片后的ByteBuf还可以继续使用。
        for(int i=0;i<sliceByteBuf.writerIndex();i++){
            System.out.print((char)sliceByteBuf.getByte(i));
        }
        sliceByteBuf.release();
    }

复制ByteBuf(物理上的)

    /**
     *copy()方法对ByteBuf进行了深拷贝,是物理上的复制,与原来的ByteBuf无关联。
     */
    public void copyTest(){
        //创建一个初始容量为10字节大小的ByteBuf
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer(10);
        byteBuf.writeBytes(new byte[]{'1','2','3','4','5','6','7','8','9','0'});
        //复制整个原始的ByteBuf。
        ByteBuf cp =byteBuf.copy();
        cp.setByte(0,'b');
        for(int i=0;i<cp.writerIndex();i++){
            System.out.print((char)cp.getByte(i));
        }
        System.out.println();
        //从下标2开始复制,取5个长度。
        ByteBuf cp2 = byteBuf.copy(2, 5);
        cp2.setByte(0,'a');
        for(int i=0;i<cp2.writerIndex();i++){
            System.out.print((char)cp2.getByte(i));
        }
        System.out.println();
        for(int i=0;i<byteBuf.writerIndex();i++){
            System.out.print((char)byteBuf.getByte(i));
        }
    }

组合多个ByteBuf

    /**
     * 组合多个ByteBuf
     * 方法1:通过writeBytes方法组合多个ByteBuf。新组合的ByteBuf与之前的ByteBuf完全独立开。
     */
    public void writeBytesTest(){
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer(5);
        byteBuf.writeBytes(new byte[]{'1','2','3','4','5'});
        ByteBuf byteBuf2 = ByteBufAllocator.DEFAULT.directBuffer(5);
        byteBuf2.writeBytes(new byte[]{'6','7','8','9','0'});
        //
        ByteBuf byteBuf3 = ByteBufAllocator.DEFAULT.directBuffer(10);
        byteBuf3.writeBytes(byteBuf).writeBytes(byteBuf2);
        for(int i=0;i<byteBuf3.writerIndex();i++){
            System.out.print((char)byteBuf3.getByte(i));
        }
    }

    /**
     * 组合多个ByteBuf
     * 方法2:逻辑上的组合。
     */
    public void componentTest(){
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer(5);
        byteBuf.writeBytes(new byte[]{'1','2','3','4','5'});
        ByteBuf byteBuf2 = ByteBufAllocator.DEFAULT.directBuffer(5);
        byteBuf2.writeBytes(new byte[]{'6','7','8','9','0'});
        CompositeByteBuf compositeByteBuf = ByteBufAllocator.DEFAULT.compositeBuffer();
        //调整addComponents()方法的默认参数。如果第一个参数是默认的,则读写指针都会为0。
        compositeByteBuf.addComponents(true,byteBuf,byteBuf2);
        compositeByteBuf.retain();
        System.out.println(compositeByteBuf);
        for(int i=0;i<compositeByteBuf.writerIndex();i++){
            System.out.print((char)compositeByteBuf.getByte(i));
        }
        System.out.println();
        //修改其中一个ByteBuf,观察是否会影响CompositeByteBuf。
        byteBuf.setByte(0,'a');
        for(int i=0;i<compositeByteBuf.writerIndex();i++){
            System.out.print((char)compositeByteBuf.getByte(i));
        }
    }
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值