暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

图文解析netty之buffer内部的数据结构

1024行日记 2021-07-06
1269

这篇文章会介绍netty-buffer里的数据结构及其设计,会涉及各个数据机构之间的关系。

本文主要介绍netty-buffer模块中SizeClassSizeClassesMemoryRegionCachePoolArena的数据结构和存储方式。

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<Textends 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这个链表中。
在每一次添加时都会校验他们的闲置率,如果闲置率超过了一定大小,就会移动到下一个链表中。


文章转载自1024行日记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论