netty源码解析(4.0)-21 ByteBuf的设计原理
io.netty.buffer包中是netty ByteBuf的实现。ByteBuf是一个二进制缓冲区的抽象接口,它的功能有:
- 可以随机访问、顺序访问。
- 支持基本数据类型(byte, short, int, long, float, double)的序列化和反序列化。
- 不限制底层原始的数据类型,可以是byte[], NIO Buffer, String, IO Stream, 直接内(C语言中可以表示为指向一块内置起始地址的指针void*, 内存的长度), 等等。
为什么需要ByteBuf
缓冲区的使用范围非常广泛:I/O操作,序列化/反序列化,编码转换,压缩/解压,加/解密等所有需要使用byte[]的场景。
有些场景需要需要能够快速地创建和销毁缓冲区,如:高并发的服务器,处理请求、返回响应的时时候需要大量且高频地创建,销毁缓冲区。
有些场景不能确定需要多大的缓冲区,如: 从数据流中分离出一条消息,消息的长度不确定,只知道最大长度。假如消息的最大长度是64KB,而消息的平均长度只有4KB, 如果每次创建64KB的缓冲区就太浪费了。如果缓冲区能够在需要的时候自动且高效地增减容量,就完美了。
在所有的场景中都涉及到频繁的数据copy,这要求缓冲区数据copy的性能要尽量高。如果有可能,尽量减少数据copy。
ByteBuf就是为解决以上问题而设计的。
核心概念
如下所示
| discardable bytes | readable bytes | writable bytes |
0 readerIndex writerIndex capacity
- capacity: 缓冲区的容量。
- readerIndex: 当前读的位置。可以使用readerIndex()和readerIndex(int)方法获取、设置readerIndex值。每次调用readXXX方法都会导致readerIndex向writerIndex移动,直到等于writerIndex为止。
- writerIndex: 写的当前位置。可以使用writerIndex()和writerIndex(int)方法获取、设置writeIndex的值。每次调用writeXXX方法都会导致writeIndex向capacity移动,直到等于capacity为止。
- discardable bytes: 可丢弃的数据。0--readerIndex之间的数据, 长度是readerIndex - 0,调用discardReadBytes会丢弃这部分数据,把readerIndex--writerIndex之间的数据移动到ByteBuf的开始位置(0), ByteBuf会变成如下所示的样子:
| readable bytes | writable bytes |
readerIndex(0) writerIndex capacity
- readable bytes: 可读数据。 readerIndex--writerIndex之间的数据,长度是writerInex - readerIndex。可以调用readableBytes()方法得到它的长度。
- writeable bytes: 可写的空间。长度是capacity - writerIndex。也可以认为它是ByteBuf的剩余空间。
核心能力
对二进制数据的读写是ByteBuf的核心能力。它提供两种读写方式:
- 随机读写: getXXX(int), setXXX(int, ...)。不会对readerIndex和writerIndex产生影响,范围是(0,capacity]。
- 顺序读写: readXXX, 增加readerIndex的值,范围是(readerIndex, writerIndex]。 writeXXX,增加writerIndex值,范围是(writerIndex, capacity]。
ByteBuf为了方便使用,提供了一些基本数据类型(unsigned表示无符号类型)的读写支持:
| 数据类型 | 长度(Byte) |
|---|---|
| byte, unsignedByte | 1 |
| short, unsignedShort | 2 |
| char | 2 |
| medium, unsignedMedium | 3 |
| int, unsignedInt | 4 |
| long | 8 |
| float | 4 |
| double | 8 |
对这些基本数据类型的读写涉及到了字节须的问题,ByteBuf支持两种字节序,使用java.nio.ByteOrder中的定义,默认的字节序是BIG_ENDIAN, 这个也是网络字节序。
此外还提供了对byte[]类型及可以当成byte[]使用的数据类型的支持, 方法名都是:getBytes,setBytes, readBytes, writeBytes。
- byte[]
- ByteBuf
- ByteBuffer
- GatheringByteChannel
- InputStread, OutputStream
内存管理
内存管理分为两个部分:内存分配,内存释放。
ByteBufAllocator是内存分配的接口,它有两个具体实现:UnpooledByteBufAllocator, PooledByteBufAllocator。
UnpooledByteBufAllocator是JVM内存分配接口的简单封装。
PooledByteBufAllocator在JVM内存分配的基础上实现了一套独立的内存分配算法。
内存释放的关键是如何判定对象死亡,ByteBuf继承了ReferenceCounted接口,使用引用计数的方式判定对象死亡。
PooledByteBufAllocator中高效的内存管理算法是ByteBuf的性能基础,理解了它的算法是理解ByteBuf的关键。
体系结构
B[ByteBuf]-->AB[AbstractByteBuf];
B-->SB[SwappedByteBuf];
B-->WB[WrappedByteBuf];
AB[AbstractByteBuf]-->ARCB[AbstractReferenceCountedByteBuf];
ARCB-->CB[CompositeByteBuf<br>FixedCompositeByteBuf];
ARCB-->PB[PooledByteBuf<T>];
ARCB-->UBB[UnpooledDirectByteBuf<br>UnpooledHeapByteBuf<br>UnpooledUnsafeDirectByteBuf<br>UnpooledUnsafeHeapByteBuf<br>UnpooledUnsafeNoCleanerDirectByteBuf];
ARCB-->ROBB[ReadOnlyByteBufferBuf];
PB[PooledByteBuf<T>]-->PDB[PooledDirectByteBuf];
PB-->PUDB[PooledUnsafeDirectByteBuf];
PB-->PHB[PooledHeapByteBuf];
PHB-->PUHB[PooledUnsafeHeapByteBuf];
上图是ByteBuf体系结构中主要的类和接口。主要分为三大类:
- AbstractReferenceCountedByteBuf及其子类。这个类别是是ByteBuf中最重要的部分,它分为4个小类别:
CompositeByteBuf, FixedCompositeByteBuf: 把不同的ByteBuf组合成一个ByteBuf。
PooledByteBuf:实现了自定义内存管理算法的。
UnpooledXXXX: 直接使用JVM内存管理能力。
ReadOnlyByteBufferBuf: 只读的。
- SwappedByteBuf: 用来转换ByteBuf的字节序。
- WrappedByteBuf: 用来包装另一个ByteBuf。
工具
有两个工具类帮助开发者使用ByteBuf:
- ByteBufUtil: 这个类中创建好了默认的ByteBufAllocator,可以直接拿来用。还有一些操作ByteBuf常用的方法。
- Unpooled: 这个类是针对UnpooledXXXByteBuf的工具。
用法
创建ByteBufAllocator
使用ByteBufUtil.DEFAULT_ALLOCATOR得到ByteBufAllocator实例。这个实例可能是UnpooledByteBufAllocator,也可能是PooledByteBufAllocator类型,这取决于io.netty.allocator.type属性的设置。默认是unpooled,UnpooledByteBufAllocator类型。如果想要PooledByteBufAllocator类型,把这个属性的值设置成pooled:
java -Dio.netty.allocator.type=pooled,或者System.setProperty("io.netty.allocator.type", "pooled")
创建ByteBuf
netty不建议直接创建创建ByteBuf实例,推荐使用ByteBufAllocator创建ByteBuf实例,或者使用Unpooled静态方法。
ByteBufAllocator有7种方法用于创建ByteBuf实例:
| 方法名 | 特性 |
|---|---|
| buffer | 使用可能是JVM堆内存或直接内存,取决于具体的实现 |
| ioBuffer | 如果可以的话优先使用直接内存 |
| heapBuffer | 使用堆内存 |
| directBuffer | 使用直接内存 |
| CompositeByteBuf | 使用可能是JVM堆内存或直接内存,取决于具体的实现 |
| compositeHeapBuffer | 使用堆内存 |
| compositeDirectBuffer | 使用堆内存 |
Unpooled创建ByteBuf实例的方法有2两种:
| 方法名 | 特性 |
|---|---|
| buffer | 使用堆内存 |
| directBuffer | 使用直接内存 |
包装成ByteBuf
Unpooled提供了一系列的wrappedBuffer方法,把一些数据类型包装成一个ByteBuf, 这些数据类型有:
- byte[]。
- ByteBuffer。
- 另一个的ByteBuf中的可读数据。
wrappedBuffer方法还可以把byte[].., ByteBuffer.., ByteBuf..包装成一个CompositeByteBuf。
数据读写
数据读写是ByteBuf的基本功能,前面已经讲过,相关方法是: getXXX, readXXX, setXXX, writeXXX。
netty源码解析(4.0)-21 ByteBuf的设计原理的更多相关文章
- netty源码解析(4.0)-27 ByteBuf内存池:PoolArena-PoolThreadCache
前面两章分析的PoolChunk和PoolSubpage,从功能上来说已经可以直接拿来用了.但直接使用这个两个类管理内存在高频分配/释放内存场景下会有性能问题,PoolChunk分配内存时算法复杂度最 ...
- netty源码解析(4.0)-28 ByteBuf内存池:PooledByteBufAllocator-把一切组装起来
PooledByteBufAllocator负责初始化PoolArena(PA)和PoolThreadCache(PTC).它提供了一系列的接口,用来创建使用堆内存或直接内存的PooledByteBu ...
- netty源码解析(4.0)-26 ByteBuf内存池:PoolArena-PoolSubpage
PoolChunk用来分配大于或等于一个page的内存,如果需要小于一个page的内存,需要先从PoolChunk中分配一个page,然后再把一个page切割成多个子页-subpage,最后把内存以s ...
- netty源码解析(4.0)-29 Future模式的实现
Future模式是一个重要的异步并发模式,在JDK有实现.但JDK实现的Future模式功能比较简单,使用起来比较复杂.Netty在JDK Future基础上,加强了Future的能力,具体体现在: ...
- Netty源码分析第5章(ByteBuf)---->第2节: ByteBuf的分类
Netty源码分析第五章: ByteBuf 第二节: ByteBuf的分类 上一小节简单介绍了AbstractByteBuf这个抽象类, 这一小节对其子类的分类做一个简单的介绍 ByteBuf根据不同 ...
- Netty源码分析第5章(ByteBuf)---->第1节: AbstractByteBuf
Netty源码分析第五章: ByteBuf 概述: 熟悉Nio的小伙伴应该对jdk底层byteBuffer不会陌生, 也就是字节缓冲区, 主要用于对网络底层io进行读写, 当channel中有数据时, ...
- Netty源码分析第5章(ByteBuf)---->第3节: 缓冲区分配器
Netty源码分析第五章: ByteBuf 第三节: 缓冲区分配器 缓冲区分配器, 顾明思议就是分配缓冲区的工具, 在netty中, 缓冲区分配器的顶级抽象是接口ByteBufAllocator, 里 ...
- Netty源码分析第5章(ByteBuf)---->第4节: PooledByteBufAllocator简述
Netty源码分析第五章: ByteBuf 第四节: PooledByteBufAllocator简述 上一小节简单介绍了ByteBufAllocator以及其子类UnPooledByteBufAll ...
- Netty源码分析第5章(ByteBuf)---->第5节: directArena分配缓冲区概述
Netty源码分析第五章: ByteBuf 第五节: directArena分配缓冲区概述 上一小节简单分析了PooledByteBufAllocator中, 线程局部缓存和arean的相关逻辑, 这 ...
随机推荐
- CodeForces - 556D Case of Fugitive (贪心+排序)
Andrewid the Android is a galaxy-famous detective. He is now chasing a criminal hiding on the planet ...
- [ASP.NET Core 3框架揭秘] 文件系统[3]:物理文件系统
ASP.NET Core应用中使用得最多的还是具体的物理文件,比如配置文件.View文件以及作为Web资源的静态文件.物理文件系统由定义在NuGet包"Microsoft.Extension ...
- Visual Studio中相对路径中的宏定义
$(RemoteMachine) 设置为“调试”属性页上“远程计算机”属性的值.有关更多信息,请参见更改用于 C/C++ 调试配置的项目设置. $(References) 以分号分隔的引用列表被添加到 ...
- 二维码生成插件qrious
1.qrious是基于canvas的纯JS二维码生成插件 1.1什么是二维码 二维码又称QR Code,QR全称Quick Response,是一个近几年来移动设备上超流行的一种编码方式,它比传统的B ...
- centos7下MySQL5.7修改默存储路径
安装MySQL中YUM默认安装路径是/var/lib/mysql下,有时候该目录分配的磁盘空间并不大,需要迁移到新的磁盘目录 df -h 查看磁盘空间大小 本次迁移到 /home目录下 1.在hom ...
- Unity-使用UnityRemote调试手机Android Studio打印日志
抛出问题: 1.Unity调试Android的时候默认情况是build打包成apk,安装到手机运行,调试起来很不方便,如何快速在手机上运行呢? 2.如何像unity的console一样在andro ...
- 参加杭州 2019 AI Bootcamp有感与总结(2)
接上篇 参加杭州 2019 AI Bootcamp有感与总结(1) - repeatedly - 博客园 午餐畅谈的收获 先感谢主办方提供的午餐,中午午休的时候,大家聊了很多,或者说主要是听大佬谈.聊 ...
- linux常用命令补充
linux补充 apt 是在我们Linux系统安装软件 pip 用来安装python3的模块(第三方库) ps # 查看运行进程(pid) ps aux # 查看全部任务进程 top # 也可以查看 ...
- Java连载60-类之间的六种关系
一.类之间的关系 1.泛化关系(UML图:实线空心三角形箭头,箭头指向被继承方) 类和类.接口与接口之间的继承称为泛化关系 public class A {} class B extends A{} ...
- Jmeter录制后的脚本调优
当我们通过badboy或者HTTP代理服务器的方式录制的脚本,会发现脚本杂乱无章,图片.css.html以及各种我们不关心的脚本,因此就需要针对录制后的脚本进行调优 1.去除图片.html/css等不 ...