深入Netty的缓冲区分配与管理-Special ByteBuffer
Posted 京东虚拟平台
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入Netty的缓冲区分配与管理-Special ByteBuffer相关的知识,希望对你有一定的参考价值。
作为一个NIO通讯框架,Netty在网络传输过程中要使用大量的缓冲区(ByteBuf)来进行数据的接收和发送,但是JDK API提供的ByteBuffer类在使用过程中操作方式比较复杂,特别是标志字段多且其含义晦涩,使用时容易出错,为了提高内存使用效率,方便操作,Netty实现了一套自己的缓冲区即ByteBuf。
1
ByteBuffer与ByteBuf
ByteBuffer的基本结构
① capacity,ByteBuffer最大能容纳的Byte个数,一般通过allocate或wrap方法分配后不能改变;
② position,读写开始位置,对ByteBuffer的读写字操作从此位置开始;
③ limit,读写边界,读写ByteBuffer的时候当position到达limit的位置时即表示读完或写满;
④ mark,position位置的标记,用来在未来的某一时刻恢复position位置。
ByteBuffer的操作及内部状态
① put操作,写入字节,下面是一个典型的写入3个字节后的ByteBuffer字节排布(蓝色表示有效字节)
② flip,读之前需要调用flip方法,也就是将position复位到0,并将limit设置到position的位置(注:调用flip时mark会复位-1)
③ get,读取字节数据,会从position位置开始,下图是读取两个字节后的排布
④ clear,将position位置设为0,limit设为capacity,并不清除内容
可以看到ByteBuffer的标志总共有4个,状态切换比较多,在使用中要时刻关注这些状态对应的标志位,而且以上只是最基本的读写操作,还没有考虑压缩和容量扩展。
ByteBuf的内部结构
① ByteBuf通过两个指针的协作来完成读写操作,读操作使用readerIndex,写操作使用writerIndex;
② 初始状态的readerIndex、writerIndex值都是0,写入数据时writerIndex增加,读取数据时readerIndex增加;
③ 读写过程中readerIndex不会超过writerIndex,介于readerIndex和writerIndex的是已写入但未读取的字节。
ByteBuf的基本操作及内部状态
① set/writeXX写入3个字节后的状态
② get/readXX读取两个字节后的状态
我们可以看到Netty提供的ByteBuf比JDK原生的ByteBuffer更简单易用,内部字段的含义也比较明确
ByteBuf的类型
① UnpooledHeapByteBuf,在堆内存上分配的非池化的ByteBuf,使用后直接丢弃,不会重复使用
② PooledHeapByteBuf,池化的在堆内存中分配的 ByteBuf,使用后会被回收重复利用
③ UnpooledDirectByteBuf,直接内存中分配的ByteBuf,不会被重复使用
④ PooledDirectByteBuf,池化的直接内存中分配的ByteBuf,使用后放入池中可以被重复使用
2
ByteBuf的分配
ByteBufAllocator
为了减少分配和释放内存的开销,Netty 通过ByteBufAllocator类来分配堆或直接内存池化的ByteBuf,在业务代码中我们可以通过以下方式来得到 ByteBufAllocator 的引用
Channel channel = ...;
ByteBufAllocator allocator = channel.alloc();
//或
ChannelHandlerContext ctx = ...;
ByteBufAllocator allocator2 = ctx.alloc();
如果业务代码中无法引用 ByteBufAllocator,Netty 提供一个实用工具类Unpooled来分配非池化的ByteBuf
ByteBufAllocator allocator = Unpooled.wrappedBuffer()
....
在IO处理层我们可以在初始化Channel时配置Allocator来应用具体的ByteBuf类型
bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
....
3
ByteBuf背后的内存组织
内存管理单位
① Netty将内存划分为Arena、ChunkList、Chunk、Page、Subpage共5个层次, 在Netty中并不存在Arena这个类,内存区域的管理是由PoolArena类来实现的,下面提到的Arena都指PoolArena;
② Page是内存的最小分配单位,默认Page的大小是8K,可以通过配置启动参数设定;
③ Subpage是内存最小使用单位,一个Page可以包含0个或多个Subpage,Subpage的大小取决于首次从该Page分配内存的大小,如果首次在该Page中需要分配1k字节,那么该Page就被分为8个Subpage,每个Subpage大小为1k;
④ Chunk是由连续的Page组成,默认Chunk的大小是16M,也就是说1个Chunk有2048个Page;
⑤ ChunkList包含多个Chunk,每个ChunkList里包含的Chunk数量会动态变化;
⑥ Arena代表1个内存区域,在 Netty中内存池是由多个Arena组成的数组(可以通过运行参数配置Arena的个数),分配时会为每个线程按照轮询策略选择1个Arena,需要注意的是多个线程可能共用一个Arena。
自顶向下的组织策略
① 1个Arena由2个PoolSubpage数组和6个ChunkList组成,两个PoolSubpage数组分别为tinySubpagePools和smallSubpagePools,6个ChunkList是按照其中Chunk的内存利用率来划分的,每个ChunkList是一个双向链表结构;
② tinySubpagePools和smallSubpagePools都是链表数组结构,tinySubpagePools用来保存大小为16-496B(16->32->48->…->496)的内存块链表,共有32个这样的链表,而smallSubpagePools用保存512-4096B的内存块链表,每个链表分配的内存块大小成倍增长(512->1024->2048->4096),数组长度为4;
③ 每个ChunkList里包含多个Chunk,每个Chunk里包含多个Page(默认2048个),每个Page(默认大小为8K字节)由多个Subpage组成;
④ Chunk的内存利用率会随着内存的分配和回收而发生变化,所以每个ChunkList里包含的Chunk数量会动态变化:
qInit:内存利用率0-25%的Chunk
q000:内存利用率1-50%的Chunk
q025:内存利用率25-75%的Chunk
q050:内存利用率50-100%的Chunk
q075:内存利用率75-100%的Chunk
q100:内存利用率100%的Chunk
4
ByteBuf的分配算法
内存大小规格化及分配策略
① 分配ByteBuf时会将申请的大小规格化为2的幂次方并且最小为16B,也就是说如果我们申请19B,规格化后的大小为32B,如果我们申请8B,规格化后的大小为16B;
②分配时对于小于页大小的内存,会在tinySubpagePools或smallSubpagePools中分配,tinySubpagePools用于分配小于512字节的内存,smallSubpagePools用于分配大于512字节小于页的内存;
③ 对大于pageSize小于chunkSize的内存分配请求,会在PoolChunkList的Chunk中分配。
分配过程中
① 为了在Chunk中快速搜索满足大小的内存,Chunk以2048个页面为基础,自底向上,每一层节点作为上一层的子节点构造出一棵满二叉树,用一个数组来表示二叉树,数组元素的值为结点所在的深度,结点旁边的数字为数组的下标,结点内的数字为数组元素的值即深度,为了表达深度和数组下标的关系(n=2的d次方),元素[0]不使用,每个叶子结点标志一个Page的使用状态,根结点就标识整个Chunk的使用状态,例如初始状态,[512] =9(等于结点所在的深度),表示512节点下所有的子节点都未分配过;
② page0分配之后[512] =10(大于结点所在的深度), 则表示512节点下有子节点已经分配过,该节点不能直接被分配,而其子节点中的第10层和10层以下还存在未分配的节点;
③ page0和page1都被分配后[512] = 12 (即总层数 + 1), 可分配的深度已经大于总层数, 则该节点下的所有子节点都已经被分配。
5
总结
① 在使用方式和应用场景上,ByteBuf比JDK原生的ByteBuffer更简单易用,由于摆脱了堆内存的约束,其提供的功能更加灵活多样 ;
② Netty对ByteBuf的使用和管理统筹了堆内存和直接内存,提供了比较完善的数据结构和算法来支撑对ByteBuf的分配和管理,在高并发的场景下可以极大的提高内存的使用效率。
以上是关于深入Netty的缓冲区分配与管理-Special ByteBuffer的主要内容,如果未能解决你的问题,请参考以下文章