Netty源码之SizeClasses(4.1.58)

Netty源码之SizeClasses(4.1.58)

概述

前面已对 Netty 使用 jemalloc3(jemalloc3、jemalloc4 指代 Netty 实现的 Java 版本,而非 C) 实现的内存分配的思路以及源码进行详解,接下来的这两篇是详解 Netty 基于 jemalloc4 重构内存分配的思想以及源码。jemalloc4 相较于 jemalloc3 最大的提升是进一步优化内存碎片问题,因为在 jemalloc3 中最多可能会导致 50% 内存碎片,但 jemalloc4 通过划分更细粒度的内存规格在一定程度上改善了这一问题,这也是 SizeClasses 的由来。相关问题可见 Netty issue 10267jemalloc changeLog
Netty 重构了和内存分配相关的核心类,比如 PoolArena、PoolChunk、PoolSubpage 以及和缓存相关的 PoolThreadCache,并且新增了一个 SizeClasses 类。从整体上看,Netty 分配内存的逻辑是和 jemalloc3 大致相同:

  1. 首先尝试从本地缓存中分配,分配成功则返回。
  2. 分配失败则委托 PoolArena 进行内存分配,PoolArena 最终还是委托 PoolChunk 进行内存分配。
  3. PoolChunk 根据内存规格采取不同的分配策略。
  4. 内存回收时也是先通过本地线程缓存回收,如果实在回收不了或超出阈值,会交给关联的 PoolChunk 进行内存块回收。

jemalloc4 主要是对 PoolChunk 的内存分析进行了重构,这是我们这两篇文章分析的重点类。但是在分析它之前我们还需要对 SizeClasses 这个规格类进行讲解。
在旧版本中,对内存规格是按下图划分的:
在这里插入图片描述

仔细发现,在 Small 级别的内存分配中会存在大量的内存碎片: 比如用户申请内存大小为 1025,按 jemalloc3 算法会向 PoolChunk 申请 2048Byte 的内存块,这将会导致 50% 内存碎片。那我们看看 jemalloc4 是如何解决的。
在这里插入图片描述

从上图可以看出,jemalloc4 返回的规格值为 1280,因此大大减少内存碎片。也可以看出,jemalloc4 取消了 Tiny 级别,如今只有 Small、Normal 和 Huge,而 SizeClasses 就是记录 Small 和 Normal 规格值的一张表(table),这张表记录了很多有用的信息。

SizeClasses

这是一个极其重要类,它在内部维护一个二维数组,这个数组存储与内存规格有关的详细信息。我们先看看这张表长什么样子的:

indexlog2Grouplog2DeltanDeltaisMultiPageSizeisSubPagelog2DeltaLookup
0440014
1441014
2442014
3443014
4641014
5642014
6643014
7644014
8751015
9752015
10753015
11754015
12861016
13862016
14863016
15864016
16971017
17972017
18973017
19974017
201081018
211082018
221083018
231084018
241191019
251192019
261193019
271194019
2812101010
2912102010
3012103010
3112104110
3213111010
3313112010
3413113010
3513114110
3614121010
3714122110
3814123010
3914124100
4015131100
4115132100
4215133100
4315134100
4416141100
4516142100
4616143100
4716144100
4817151100
4917152100
5017153100
5117154100
5218161100
5318162100
5418163100
5518164100
5619171100
5719172100
5819173100
5919174100
6020181100
6120182100
6220183100
6320184100
6421191100
6521192100
6621193100
6721194100
6822201100
6922202100
7022203100
7122204100
7223211100
7323212100
7423213100
7523214100

从上表中可知,数组长度 76。每一列表示的含义如下:

  • index: 由 0 开始的自增序列号,表示每个 size 类型的索引。
  • log2Group: 表示每个 size 它所对应的组。以每 4 行为一组,一共有 19 组。第 0 组比较特殊,它是单独初始化的。因此,我们应该从第 1 组开始,起始值为 6,每组的 log2Group 是在上一组的值 +1。
  • log2Delta: 表示当前序号所对应的 size 和前一个序号所对应的 size 的差值的 log2 的值。比如 index=6 对应的 size = 112,index=7 对应的 size= 128,因此 index=7 的 log2Delta(7) = log2(128-112)=4。不知道你们有没有发现,其实 log2Delta=log2Group-2
  • nDelta: 表示组内增量的倍数。第 0 组也是比较特殊,nDelta 是从 0 开始 + 1。而其余组是从 1 开始 +1。
  • isMultiPageSize: 表示当前 size 是否是 pageSize(默认值: 8192) 的整数倍。后续会把 isMultiPageSize=1 的行单独整理成一张表,你会发现有 40 个 isMultiPageSize=1 的行。
  • isSubPage: 表示当前 size 是否为一个 subPage 类型,jemalloc4 会根据这个值采取不同的内存分配策略。
  • log2DeltaLookup: 当 index<=27 时,其值和 log2Delta 相等,当index>27,其值为 0。但是在代码中没有看到具体用来做什么。

有了上面的信息并不够,因为最想到得到的是 index 与 size 的对应关系。
在 SizeClasses 表中,无论哪一行的 size 都是由 _size = (1 << log2Group) + nDelta * (1 << log2Delta)_ 公式计算得到。因此通过计算可得出每行的 size:

indexlog2Grouplog2DeltanDeltaisMultiPageSizeisSubPagelog2DeltaLookupsizeUnit: MB
044001416
144101432
244201448
344301464
464101480
564201496
6643014112
7644014128
8751015160
9752015192
10753015224
11754015256
12861016320
13862016384
14863016448
15864016512
16971017640
17972017768
18973017896
199740171024
2010810181280
2110820181536
2210830181792
2310840182048
2411910192560
2511920193072
2611930193584
2711940194096
28121010105120
29121020106144
30121030107168
311210411081928KB
32131110101024010KB
33131120101228812KB
34131130101433614KB
35131141101638416KB
36141210102048020KB
37141221102457624KB
38141230102867228KB
39141241003276832KB
40151311004096040KB
41151321004915248KB
42151331005734456KB
43151341006553664KB
44161411008192080KB
45161421009830496KB
4616143100114688112KB
4716144100131072128KB
4817151100163840160KB
4917152100196608192KB
5017153100229376224KB
5117154100262144256KB
5218161100327680320KB
5318162100393216384KB
5418163100458752448KB
5518164100524288512KB
5619171100655360640KB
5719172100786432768KB
5819173100917504896KB
591917410010485761.0MB
602018110013107201.25MB
612018210015728641.5MB
622018310018350081.75MB
632018410020971522MB
642119110026214402.5MB
652119210031457283MB
662119310036700163.5MB
672119410041943044MB
682220110052428805MB
692220210062914566MB
702220310073400327MB
712220410083886088MB
72232111001048576010MB
73232121001258291212MB
74232131001468006414MB
75232141001677721616MB

从表中可以发现,不管对于哪种内存规格,它都有更细粒度的内存大小的划分。比如在 512Byte~8192Byte 范围内,现在可分为 512、640、768 等等,不再是 jemalloc3 只有 512、1024、2048 … 这种粒度比较大的规格值了。这就是 jemalloc4 最大的提升。

size = (1 << log2Group) + nDelta * (1 << log2Delta)

我们可以简单研究一下这个公式,这个公式就是通过 SizeClasses 记录的信息计算对应的 size 大小。至于如何得到这一串的公式我觉得不必深究,只需要这样做是为了更细粒度拆分内存块,以免减少内存碎片。
在这里插入图片描述

SizeClasses 体系结构

我们可以把 SizeClasses 看成是一个数组结构,最重要是存储数组索引 index 和 size 的映射关系。当然,还维护了其他数组以避免多次计算。我们先对 SizeClasses 的结构有一个大致的了解,后面再了解重要的 API。
在这里插入图片描述

PoolArena 这个大管家通过继承 SizeClasses 拥有内部的数据结构,可以直接调用相关 API。接口 SizeClassMetric 定义了与 SizeClasses 相关的核心的 API。

SizeClassesMetric

在这里插入图片描述

上面简单对 SizeClassesMetric 核心 API 做了简要的说明,相关源码这里就不进行说明了(其实我也不太懂,2333)。只需要知道Netty 通过 SizeClasses 类对内存的大小进行更细粒度的划分,从而减少内部碎片即可,后续 Netty 会通过 size 找到索引值 index,也可以通过 index 找到对应的 size。

isMultiPageSize=1

抽取 SizeClasses 中 isMultiPageSize=1 的所有行组成下面的表格。每列表示含义解释如下:

  • index: 对应 SizeClasses 的 index 列。
  • size: 规格值。
  • num of page: 包含多少个 page。
  • 对应 SizeClasses#pageIdx2SizeTab 的索引值。
indexsizenum of pageindex of pageIdx2sizeTab
31819210
351638421
372457632
3932KB43
4040KB54
4148KB65
4256KB76
4364KB87
4480KB108
4596KB129
46112KB1410
47128KB1611
48160KB2012
49192KB2413
50224KB2814
51256KB3215
52320KB4016
53384KB4817
54448KB5618
55512KB6419
56640KB8020
57768KB9621
58896KB11222
591.0MB12823
601.25MB16024
611.5MB19225
621.75MB22426
632MB25627
642.5MB32028
653MB38429
663.5MB44830
674MB51231
685MB64032
696MB76833
707MB89634
718MB102435
7210MB128036
7312MB153637
7414MB179238
7516MB204839

SizeClasses#pageIdx2sizeTab

有很多同学对这个数组表示疑惑,这个数组可以用来做些什么? 这个数组用来加速 size<=lookupMaxSize(默认值: 4096) 的索引查找。也就是说,当我们需要通过 size 查找 SizeClasses 对应的数组索引时,如果此时 size<=lookupMaxSize 成立,那么经过计算得到相应 pageIdx2sizeTab 的索引值,然后获取存储在 pageIdx2sizeTab 的值就是对应 SizeClasses 的 index。
那如何计算得到相应 pageIdx2sizeTab 的索引值呢? 是由 idx=(size-1)/16 求得。比如当 size=4096,由公式求得 idx=255,此时 pageIdx2sizeTab[255]=27,因此 size=4096 对应的 SizeClasses 索引值为 27。

总结

我并没有对 SizeClasses 的源码进行分析,主要是分析了也没有用,里面的代码比较晦涩,就算弄懂了也就那么一回事,我们只注意最终的表结构就 OK 了。

我的公众号

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值