Netty组件之ByteBuf详解 源码分析
学习了前面的一些netty组件,此篇将讲解最后一个组件ByteBuf,ByteBuf是对Nio的ByteBuffer的一个增强。
1.创建ByteBuf对象
ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
这是最基本的创建方式,我们也可指定其初始容量和最大容量(可扩容)。
首先概要看看源码,有一个大致的了解:
点进我们上面的buffer方法,源码的关键代码如下
继续查看AbstractByteBufAllocator子实现抽象类,对应三个buffer方法的重载并判断需要创建直接内存或是堆内存,和NIO的ByteBuffer雷同:
继续查看分配的一些细节,点入分配的方法,可以看到一些初始值
进入关键方法,newHeaoBuffer方法,如下,是两个抽象方法,我们可以看到下面的两个实现类
2.直接内存vs堆内存
分配两种不同空间的方式如下,底层就是上面看过的源码。其中堆内存的空间分配空间效率较快但是读写效率较高,直接内存相反,如果对Nio不了解的可以看看Nio的零拷贝那一篇。
ByteBuf buf = ByteBufAllocator.DEFAULT.directBuffer();ByteBufAllocator.DEFAULT.heapBuffer();
- 直接内存创建和销毁的代价昂贵,但读写性能高(少一次内存复制),适合配合池化功能一起用
- 直接内存对GC压力小,因为这部分内存不受JVM垃圾回收的管理,但也要注意及时主动释放
如果使用buffer方法默认分配的直接内存
3.池化vs非池化
这里池化的思想和数据库连接池和线程池一个思想,netty的ByteBuf也支持这一种的池化
池化的最大意义在于可以重用 ByteBuf,优点有
- 没有池化、则每次都需要创建新的ByteBuf实例,这个操作对直接内存代价昂贵,就算是堆内存,内存也会,也会增加GC的压力
- 有了池化,则可以重用池中 ByteBuf实例,并且采用了与jemalloc类似的内存分配算法提升分配效率
- 高并发时,池化功能更节约内存,减少内存溢出的可能
池化功能是否开启,可以通过下面的系统环境变量来设置(默认开启)
- 4.1以后,非 Android平台默认启用池化实现,Android平台启用非池化实现
- 4.1之前,池化功能还不成熟,默认是非池化实现
查看源码:
我们创建ByteBuf时,调用的是该接口的一个成员变量,它是ByteBufUtil类中的类变量,类型依然是ByteBufAllocator
进入ByteBufUtil找到该静态变量,并查看该类中的静态代码块
这也就验证了我们可以使用-Dio.netty.allocator.type={unpooled|pooled}来设置是否池化ByteBuf,
-D的全称是defintion,就是定义的意思,差不多应该表达的就是用户自定义参数的意思。必须加-D
需要查看当前的ByteBuf是什么类型的,只需要打印该对象的.getClass()查看是哪个类创建的即可,带pooled的即池化,带direct的即分配直接内存。如class UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf
4.组成
ByteBuf由四部分组成
也就是一边写一边读,读到后面前面的数据也就没用了,当写满后还会动态扩容,最大扩容大小可以自己设定,默认为int的最大值。此部分和ByteBuffer的区别显而易见,有两个指针,所以不需要切换读写模式,方便使用。
5.写入
方法名 | 含义 | 备注 |
---|---|---|
writeInt(int value) | 写入int值 | Big Endian,大端写入,即0x250,写入后 00 00 02 50 |
writeIntLE(int value) | 写入int值 | Little Endian,小端写入,即0x250,写入后 50 02 00 00 |
writeBoolean(int value) | 写入boolean值 | 用一字节01|00代表true|false |
writeByte(int value) | 写入byte值 | |
writeBytes(byte[] src) | 写入byte[] | |
writeBytes(ByteBuf src) | 写入netty的ByteBuf | |
writeBytes(ByteBuffer src) | 写入nio的ByteBuf | |
int writeCharSequence(CharSequence sequence,Charset charset) |
写入字符串 |
- 这些方法的未指明返回值的,其返回值都是 ByteBuf,意味着可以链式调用
- 网络传输默认习惯是 Big Endian
小端存储指从内存的低地址开始,先存储数据的低序字节再存高序字节;相反,大端存储指从内存的低地址开始,先存储数据的高序字节再存储数据的低序字节。
还有一类方法是set开头的一系列方法,也可以写入数据,但不会改变写指针位置
6.扩容
- 如果写入后数据大小未超过512,则选择下一个16的整数倍,例如写入后大小为12,则扩容后capacity是16
- 如果写入后数据大小超过512,则选择下一个2^n,例如写入后大小为513,则扩容后capacity是2^10=1024 (2^9=512已经不够了)
- 扩容不能超过max capacity会报错
7.读取
和write相对应
读过的内容,就属于废弃部分了,再读只能读那些尚未读取的部分
如果需要重复读取int整数5,怎么办?
可以在read前先做个标记mark
buf.markReaderIndex();//做一个读标记
buf.resetReaderIndex();//回到标记
除了做标记,还可以通过一个个的索引get()值
8.retain&release
由于Netty中有堆外内存的ByteBuf 实现,堆外内存最好是手动来释放,而不是等GC垃圾回收。
- UnpooledHeapByteBuf使用的是JVM内存,只需等GC回收内存即可
- UnpooledDirectByteBuf使用的就是直接内存了,需要特殊的方法来回收内存
- PooledByteBuf和它的子类使用了池化机制,需要更复杂的规则来回收内存
Netty这里采用了引用计数法来控制回收内存,每个ByteBuf都实现了ReferenceCounted引用计数法接口
- 每个ByteBuf对象的初始计数为1
- 调用release方法计数减1,如果计数为0,ByteBuf内存被回收
- 调用retain方法计数加1,表示调用者没用完之前,其它handler即使调用了release 也不会造成回收
- 当计数为0时,底层内存会被回收,这时即使ByteBuf对象还在,其各个方法均无法正常使用
源码如下:
ReferenceCounted接口,具体的实现针对不同的ByteBuf实现
谁来负责release呢?
我们都知道ByteBuf存在在一个管道里互相传递,当有哪一个handler中不在需要传递该ByteBuf时,就可以销毁了。
想象这么一个情景,我们读取到ByteBuf在h1Hadnler里把该ByteBuf转为了String,h1把String传递给h2,而不是ByteBuf,此时为了能给及时的释放,h1就应该释放此ByteBuf的分配的空间;如果h1把ByteBuf传给h2,h2继续传下去给tail,则已经到尾部,ByteBuf已经没用了,需要由tail释放。
基本规则是,谁是最后使用者,谁负责release,详细分析如下
TailContext分析
找到一个名为TailContext的类,此类就是我们最后的tail节点,如下
因为它实现了ChannelInboundHandler,所以可以找到它实现的channelRead方法,如下:
深入查看,可以看到
可以看到,如果msg是一个ReferenceCounted实现的话,Tail就会调用该该对象的release方法。
HeadContext分析
头结点不仅实现了Inboud还实现了outbound,因为HeadContex也需要做一个收尾动作,Netty 组件 Channel 、Future 、Promise_清风拂来水波不兴的博客-CSDN博客
前面说过write事件是从后往前传递,从tail开始,一直到head,head当然要留意write事件了
9.slice
【零拷贝】的体现之一,对原始ByteBuf进行切片成多个ByteBuf,切片后的ByteBuf并没有发生内存复制,还是使用原始ByteBuf的内存,切片后的 ByteBuf维护独立的read,write指针
ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(10);
buf.writeBytes(new byte[]{'a','b','c','d','e','f'});
// slice(int index, int length)从index切length个,返回一个新的ByteBuf对象
ByteBuf slice = buf.slice(0,5);
- 也就是这个新的对象和以前的公用同一段内存,任意一个改了的话其他的也会改;
- 切片后的新ByteBuf容量不能增加,因为增加的话再写数据就乱套了
- 如果切片后的ByteBuf进行了release的话,就不能再使用了,所以一般分片后都会给原始的ByteBuf进行retain,防止被回收,导致切片的使用出异常,但一定要记得release,否则会导致幽灵空间,也就是内存泄漏
10.deplicate
【零拷贝】的体现之一,就好比截取了原始ByteBuf所有内容,并且没有max capacity的限制,也是与原始ByteBuf使用同一块底层内存,只是读写指针是独立的
11.copy
会将底层内存数据进行深拷贝,因此无论读写,都与原始 ByteBuf 无关
12.composite
把多个小的ByteBuf组合成一个大的ByteBuf,也没用复制操作,但是却带来了更复杂的维护,因为这只是逻辑上的合并
ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(5);
ByteBuf buf1 = ByteBufAllocator.DEFAULT.buffer(5);
buf.writeBytes(new byte[]{1,2,3,4,5});
buf1.writeBytes(new byte[]{6,7,8,9,10});
//创建CompositeByteBuf对象
CompositeByteBuf buffer = ByteBufAllocator.DEFAULT.compositeBuffer();
//addComponents(boolean increaseWriterIndex,ByteBuf... buffers),布尔值表示组合后读写指针要更新,这是个小小的坑
buffer.addComponents(true,buf, buf1);
System.out.println(buffer);
13.UnPooled
Unpooled是一个工具类,类如其名,提供了非池化的ByteBuf创建、组合、复制等操作这里仅介绍其跟【零拷贝】相关的wrappedBuffer方法,可以用来包装ByteBuf
ByteBuf buf1 = ByteBufAllocator.DEFAULT.buffer(5);
buf1.writeBytes(new byte[]{1, 2, 3, 4, 5});
ByteBuf buf2 = ByteBufAllocator.DEFAULT.buffer(5);
buf2.writeBytes(new byte[]{6,7,8,9,10});
//当包装ByteBuf个数超过一个时,底层使用了CompositeByteBuf
ByteBuf buf3 = Unpooled.wrappedBuffer(buf1, buf2);
System.out.println(ByteBufUtil.prettyHexDump(buf3));
输出
Netty组件之ByteBuf详解 源码分析相关推荐
- SpringBoot入门详解源码分析
注:文章内容来自于黑马的虎哥,个人感觉写的挺好的,所以只是做了简单整理,我只是文章的搬运工! # 0.学习目标 - 了解SpringBoot的作用 - 掌握java配置的方式 - 了解SpringBo ...
- 史上最详细的ConcurrentHashMap详解--源码分析
ps.本文所有源码都是基于jdk1.6 首先说明一点,ConcurrentHashMap并不是可以完全替换Hashtable的,因为ConcurrentHashMap的get.clear函数是弱一致的 ...
- Java开源生鲜电商平台-Java分布式以及负载均衡架构与设计详解(源码可下载)
Java开源生鲜电商平台-Java分布式以及负载均衡架构与设计详解(源码可下载) 说明:主要是针对一些中大型的项目需要进行分布式以及负载均衡的架构提一些思路与建议. 面对大量用户访问.高并发请求,海量 ...
- 并发编程五:java并发线程池底层原理详解和源码分析
文章目录 java并发线程池底层原理详解和源码分析 线程和线程池性能对比 Executors创建的三种线程池分析 自定义线程池分析 线程池源码分析 继承关系 ThreadPoolExecutor源码分 ...
- netty中的future和promise源码分析(二)
前面一篇netty中的future和promise源码分析(一)中对future进行了重点分析,接下来讲一讲promise. promise是可写的future,从future的分析中可以发现在其中没 ...
- netty系列之:netty中的ByteBuf详解
文章目录 简介 ByteBuf详解 创建一个Buff 随机访问Buff 序列读写 搜索 其他衍生buffer方法 和现有JDK类型的转换 总结 简介 netty中用于进行信息承载和交流的类叫做Byte ...
- beaninfo详解源码解析 java_【Spring源码分析】Bean加载流程概览
代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...
- Dubbo篇:基于Netty实现Dubbo协议编解码源码分析
Dubbo协议解析 Dubbo协议设计参考了TCP/IP协议,包括协议头和协议体两部分.16字节报文头主要携带了魔法数(0xdabb,用于分割两个不同请求),以及当前请求报文是否是Request.Re ...
- linux设备驱动开发详解源码,linux设备驱动开发详解光盘源码.rar
压缩包 : linux设备驱动开发详解光盘源码.rar 列表 19/busybox源代码/busybox-1.2.1.tar.bz2 19/MTD工具/mtd-utils-1.0.0.tar.gz 1 ...
- 最全解释:Linux操作系统下的软件安装与管理详解(源码安装、rpm/dpkg、yum/apt-get安装)
在linux上安装软件,安装方式和软件包获取的途径都远远比windows的丰富,那当然这就变得复杂很多,本文旨在理解linux下繁杂的软件安装.管理原理 ,学习软件的安装方式.源码包格式.远程软件 ...
最新文章
- 分布式消息队列 — RabbitMQ(3)
- 进程的用户栈和内核栈
- SAP后台作业记录操作
- Spark SQL程序实现RDD转换DataFrame
- 在LINQ to SQL中使用Translate方法以及修改查询用SQL
- 窦学计算机基础期末考试,关于新生开学考计算机基础
- httpclient base64 文件上传_代码级别的上传下载神器
- 码农提高工作效率(转载)
- 梦断代码最后4章读后感
- 数字化转型案例:美的集团
- 通过pyproj进行WGS84到UTM坐标的转换
- oracle刷同义词报错,oracle 同义词
- 拼多多出现重大BUG,几小时内损失超千万,但处理方式让用户怒了
- ashampoo(阿香婆) movie studio视频剪辑笔记
- 微信公众平台开通业务域名
- Trying to resize storage that is not resizeable 解决
- java使用bks双向认证_GitHub - wanglijun93/RxHttpUtils: Rxjava+Retrofit封装,便捷使用
- png照片太大怎么压缩?三步轻松搞定
- 使用枚举实现编译时可变长数组
- Multisim仿真——二极管开关电路