Netty之ByteBuf应用详解
创始人
2025-05-29 03:03:37

目录

目标

概述

实战

创建直接内存的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() {@Overridepublic 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);//写入longbyteBuf.writeLong(1L);//写入字符串byteBuf.writeCharSequence("Hello World!",Charset.forName("UTF-8"));//写入StringBufferStringBuffer sb = new StringBuffer();byteBuf.writeCharSequence(sb,Charset.forName("UTF-8"));//写入java.nio.ByteBufferByteBuffer 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的内存

概述

  • 非池化的堆内存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字节大小的ByteBufByteBuf 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进行切片(逻辑上的切分)

    /*** 切分ByteBuf*/public void sliceTest(){//创建一个初始容量为10字节大小的ByteBufByteBuf 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

特别注意

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

推荐案例

    public void sliceTest(){//创建一个初始容量为10字节大小的ByteBufByteBuf 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

复制ByteBuf(物理上的)

    /***copy()方法对ByteBuf进行了深拷贝,是物理上的复制,与原来的ByteBuf无关联。*/public void copyTest(){//创建一个初始容量为10字节大小的ByteBufByteBuf 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

组合多个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

相关内容

热门资讯

【实验报告】实验一 图像的... 实验目的熟悉Matlab图像运算的基础——矩阵运算;熟悉图像矩阵的显示方法࿰...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
大模型落地比趋势更重要,NLP... 全球很多人都开始相信,以ChatGPT为代表的大模型,将带来一场NLP领...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
kuernetes 资源对象分... 文章目录1. pod 状态1.1 容器启动错误类型1.2 ImagePullBackOff 错误1....
STM32实战项目-数码管 程序实现功能: 1、上电后,数码管间隔50ms计数; 2、...
TM1638和TM1639差异... TM1638和TM1639差异说明 ✨本文不涉及具体的单片机代码驱动内容,值针对芯...
Qt+MySql开发笔记:Qt... 若该文为原创文章,转载请注明原文出处 本文章博客地址:https://h...
Java内存模型中的happe... 第29讲 | Java内存模型中的happen-before是什么? Java 语言...
《扬帆优配》算力概念股大爆发,... 3月22日,9股封单金额超亿元,工业富联、鸿博股份、鹏鼎控股分别为3.0...
CF1763D Valid B... CF1763D Valid Bitonic Permutations 题目大意 拱形排列࿰...
SQL语法 DDL、DML、D... 文章目录1 SQL通用语法2 SQL分类3 DDL 数据定义语言3.1 数据库操作3.2 表操作3....
文心一言 VS ChatGPT... 3月16号,百度正式发布了『文心一言』,这是国内公司第一次发布类Chat...
CentOS8提高篇5:磁盘分...        首先需要在虚拟机中模拟添加一块新的硬盘设备,然后进行分区、格式化、挂载等...
Linux防火墙——SNAT、... 目录 NAT 一、SNAT策略及作用 1、概述 SNAT应用环境 SNAT原理 SNAT转换前提条...
部署+使用集群的算力跑CPU密... 我先在开头做一个总结,表达我最终要做的事情和最终环境是如何的,然后我会一...
Uploadifive 批量文... Uploadifive 批量文件上传_uploadifive 多个上传按钮_asing1elife的...
C++入门语法基础 文章目录:1. 什么是C++2. 命名空间2.1 域的概念2.2 命名...
2023年全国DAMA-CDG... DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义...
php实现助记词转TRX,ET... TRX助记词转地址网上都是Java,js或其他语言开发的示例,一个简单的...
【分割数据集操作集锦】毕设记录 1. 按要求将CSV文件转成json文件 有时候一些网络模型的源码会有data.json这样的文件里...
Postman接口测试之断言 如果你看文字部分还是不太理解的话,可以看看这个视频,详细介绍postma...
前端学习第三阶段-第4章 jQ... 4-1 jQuery介绍及常用API导读 01-jQuery入门导读 02-JavaScri...
4、linux初级——Linu... 目录 一、用CRT连接开发板 1、安装CRT调试工具 2、连接开发板 3、开机后ctrl+c...
Urban Radiance ... Urban Radiance Fields:城市辐射场 摘要:这项工作的目标是根据扫描...
天干地支(Java) 题目描述 古代中国使用天干地支来记录当前的年份。 天干一共有十个,分别为:...
SpringBoot雪花ID长... Long类型精度丢失 最近项目中使用雪花ID作为主键,雪花ID是19位Long类型数...
对JSP文件的理解 JSP是java程序。(JSP本质还是一个Servlet) JSP是&#...
【03173】2021年4月高... 一、单向填空题1、大量应用软件开发工具,开始于A、20世纪70年代B、20世纪 80年...
LeetCode5.最长回文子... 目录题目链接题目分析解题思路暴力中心向两边拓展搜索 题目链接 链接 题目分析 简单来说࿰...