这篇文章会介绍netty-buffer里的数据结构及其设计,会涉及各个数据机构之间的关系。
本文主要介绍netty-buffer模块中SizeClass、SizeClasses、MemoryRegionCache、PoolArena的数据结构和存储方式。
SizeClass
io.netty.buffer.PoolArena.SizeClass
是netty中的池化的内存规则。有Small和Normall两种。
默认情况下Small的范围是0~28K。Normal是28K~16M。
设计原因是netty中内存的申请规则是一次申请一个物理内存块(16M),为了避免浪费,然后在使用时会将其切成Subpage(子页)来使用。
池化就是将缓存放在一个指定的缓存池中,等到需要的时候去取。
SizeClasses
io.netty.buffer.SizeClasses
是netty中一个重要的内存规格表。
最小为16byte。依次是16byte的倍数大小。sizeClasses就是存储这些的。
下面展示的是一个我本机默认情况下SizeClasses的存储示例。

使用说明举例,当需要分配一个25K的Bytebuf时,会去找index=38 的这一行的28K,然后进行分配。
MemoryRegionCache
io.netty.buffer.PoolThreadCache.MemoryRegionCache
是netty的内存区域块缓存。会优先从这里进行分配,如果这里分配失败,会从poolArena中进行分配。
下面展示了small类型的MemoryRegionCache。参见字段:io.netty.buffer.PoolThreadCache#smallSubPageHeapCaches
,io.netty.buffer.PoolThreadCache#smallSubPageDirectCaches
。这两个字段就是预初始化的small类型的内存缓存。从16byte开始的。
默认情况下,这个MemoryRegionCache[]的最大下标是38,也就是它内部有39个队列,每个Queue的长度为256。
它的下标计算方式是sizeClasses中的index。比如index=0的Queue中的每个Entry的大小都是16byte。

normal类型的MemoryRegionCache的数据结构,参见字段:io.netty.buffer.PoolThreadCache#normalHeapCaches
,io.netty.buffer.PoolThreadCache#normalDirectCaches
。
这两个字段就是预初始化的normal类型的内存缓存。最大为16M。
默认情况下,这个MemoryRegionCache[]的长度为36,也就是它内部有36个队列,每个Queue的长度为64。其最大在sizeClasses中的下标为75。
在计算时,需要加上small的最大下标38才能在sizeClasses中找到对应的要分配内存大小。减去38可以计算出MemoryRegionCache的下标位置。

其中的Queue中的元素是io.netty.buffer.PoolThreadCache.MemoryRegionCache.Entry
,这个类在每次使用完都会回收这个对象,然后添加到队列尾部,等待下一次使用,同时也避免GC。
PoolArena
io.netty.buffer.PoolArena
是netty中的竞争内存池,一次会申请2倍CPU核数个,但是只会取竞争最小的那个。
其类文件如下:
abstract class PoolArena<T> extends SizeClasses implements PoolArenaMetric {
// ...
private final PoolSubpage<T>[] smallSubpagePools;
// ...
private final PoolChunkList<T> q050;
private final PoolChunkList<T> q025;
private final PoolChunkList<T> q000;
private final PoolChunkList<T> qInit;
private final PoolChunkList<T> q075;
private final PoolChunkList<T> q100;
// ...
}
其中是smallSubpagePools是子页池,用于向Small类型分配。其内部每一个元素(PoolSubPage)最开始存储的相当于是一个不变的引用,而其实际使用的是引用的next节点。
另一个重要的属性就是PoolChunkList,用于向Normal类型分配。它是由PoolChunk组成的链表。PoolChunk是netty向操作系统申请到的内存块。其数据结构如下图所示。

其中,所有的白色方块都是poolchunk,netty中的内存块。
每一个红色框内都是一个poolChunk的链表,为poolChunkList。而所有的poolChunkList也是相互链接的。
那么为什么要设计成这样的呢?我们从使用开始来透析这个结构。
我们看到上面的变量都是q+数字,那个数字就是这个poolChunkList的最小使用率。
+-------+------------+------------+
| | 最小使用率 | 最大使用率 |
+---------------------------------+
| qInit | — | 25 |
+---------------------------------+
| q000 | 1 | 50 |
+---------------------------------+
| q025 | 25 | 75 |
+---------------------------------+
| q050 | 50 | 100 |
+---------------------------------+
| q075 | 75 | 100 |
+---------------------------------+
| q100 | 100 | — |
+-------+------------+------------+
最开始是没有任何内存的,此时需要申请内存。新的内存会进入到qInit这个链表中。
在每一次添加时都会校验他们的闲置率,如果闲置率超过了一定大小,就会移动到下一个链表中。




