Netty-内存池源码三 (SizeClasses 前情预热)

Netty-内存池源码三 (SizeClasses 前情预热)

内存池核心类如下:

  • PooledByteBufAllocator
  • PooledUnsafeDirectByteBuf
  • PooledUnsafeDirectByteBuf
  • PoolThreadCache
  • MemoryRegionCache
  • PoolArena
  • SizeClasses 本期介绍
  • PoolChunk
  • LongPriorityQueue
  • LongLongHashMap
  • PoolSubpage

上一期介绍了【PooledByteBufAllocator】类,从中可知它又将 内存分配的工作 交给了 【PoolArena】类

若要分析 【PoolArena】 首先要分析其父类 【SizeClasses】。

jemalloc3中 是没有【SizeClasses】类的, 而jemalloc4 相较于 jemalloc3 最大的提升是进一步优化内存碎片的问题, 这也是【SizeClasses】的由来。

在分析【SizeClasses】之前,我们需要对Netty中的 内存规格进行简单的讲解。

jemalloc3中,对内存规格的划分如下图:

Netty-内存池源码三 (SizeClasses 前情预热)

从中可发现,在Small级别的内存分配中会存在大量的内存碎片,比如 某业务要使用 1025B的内存,按照jemalloc3算法就会申请 2048B的 内存块, 这就会导致50%的内存碎片。

jemalloc4中,对内存规格的划分如下图:

Netty-内存池源码三 (SizeClasses 前情预热)

从上图可看到, 当 某业务要使用1025B的内存时,jemalloc4算法会申请1280B 的内存块,这就大大减少了内存碎片。 同样也可以看出,jemalloc4取消了 Tiny级别的内存规格。

SizeClasses】 该类的主要工作就是 按照 chunkSize 16MB 划分成了 4张表, 其实完全可以把它当作成一个虚拟的数据库,里面存在 4张表

  • sizeClasses 总表 , 维护着 各个内存规格的详细信息
  • sizeIdx2sizeTab 维护着 index 和 size的关系
  • pageIdx2sizeTab 维护着 pageSize 倍数的内存规格表
  • size2idxTab 维护着小于等于 4KB(4096) 规格的表

SizeClasses】类大致结构如下图:
Netty-内存池源码三 (SizeClasses 前情预热)

sizeClasses

该表维护了一个二维数组 ,具体如下:

index log2Group log2Delta nDelta isMultiPageSize isSubPage log2DeltaLookup size(虚) Unit
0 4 4 0 0 1 4 16
1 4 4 1 0 1 4 32
2 4 4 2 0 1 4 48
3 4 4 3 0 1 4 64
4 6 4 1 0 1 4 80
5 6 4 2 0 1 4 96
6 6 4 3 0 1 4 112
7 6 4 4 0 1 4 128
8 7 5 1 0 1 5 160
9 7 5 2 0 1 5 192
10 7 5 3 0 1 5 224
11 7 5 4 0 1 5 256
12 8 6 1 0 1 6 320
13 8 6 2 0 1 6 384
14 8 6 3 0 1 6 448
15 8 6 4 0 1 6 512
16 9 7 1 0 1 7 640
17 9 7 2 0 1 7 768
18 9 7 3 0 1 7 896
19 9 7 4 0 1 7 1024
20 10 8 1 0 1 8 1280
21 10 8 2 0 1 8 1536
22 10 8 3 0 1 8 1792
23 10 8 4 0 1 8 2048
24 11 9 1 0 1 9 2560
25 11 9 2 0 1 9 3072
26 11 9 3 0 1 9 3584
27 11 9 4 0 1 9 4096 4KB
28 12 10 1 0 1 0 5120
29 12 10 2 0 1 0 6144
30 12 10 3 0 1 0 7168
31 12 10 4 1 1 0 8192 8KB
32 13 11 1 0 1 0 10240 10KB
33 13 11 2 0 1 0 12288 12KB
34 13 11 3 0 1 0 14336 14KB
35 13 11 4 1 1 0 16384 16KB
36 14 12 1 0 1 0 20480 20KB
37 14 12 2 1 1 0 24576 24KB
38 14 12 3 0 1 0 28672 28KB
39 14 12 4 1 0 0 32768 32KB
40 15 13 1 1 0 0 40960 40KB
41 15 13 2 1 0 0 49152 48KB
42 15 13 3 1 0 0 57344 56KB
43 15 13 4 1 0 0 65536 64KB
44 16 14 1 1 0 0 81920 80KB
45 16 14 2 1 0 0 98304 96KB
46 16 14 3 1 0 0 114688 112KB
47 16 14 4 1 0 0 131072 128KB
48 17 15 1 1 0 0 163840 160KB
49 17 15 2 1 0 0 196608 192KB
50 17 15 3 1 0 0 229376 224KB
51 17 15 3 1 0 0 262144 256KB
52 18 16 1 1 0 0 327680 320KB
53 18 16 2 1 0 0 393216 384KB
54 18 16 3 1 0 0 458752 448KB
55 18 16 4 1 0 0 524288 512KB
56 19 17 1 1 0 0 655360 640KB
57 19 17 2 1 0 0 786432 768KB
58 19 17 3 1 0 0 917504 896KB
59 19 17 4 1 0 0 1048576 1.0MB
60 20 18 1 1 0 0 1310720 1.25MB
61 20 18 2 1 0 0 1572864 1.5MB
62 20 18 3 1 0 0 1835008 1.75MB
63 20 18 4 1 0 0 2097152 2MB
64 21 19 1 1 0 0 2621440 2.5MB
65 21 19 2 1 0 0 3145728 3MB
66 21 19 3 1 0 0 3670016 3.5MB
67 21 19 4 1 0 0 4194304 4MB
68 22 20 1 1 0 0 5242880 5MB
69 22 20 2 1 0 0 6291456 6MB
70 22 20 3 1 0 0 7340030 7MB
71 22 20 4 1 0 0 8388608 8MB
72 23 21 1 1 0 0 10485760 10MB
73 23 21 2 1 0 0 12582912 12MB
74 23 21 3 1 0 0 14680064 14MB
75 23 21 4 1 0 0 16777216 16MB

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

  • index: 由 0 开始的自增序列号,表示每个 size 类型的索引。

  • log2Group: 表示每个 size 它所对应的组。

    以每 4 行为一组,一共有 19 组。

    第 0 组比较特殊 值为4。

    因此,我们应该从第 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。

jemalloc4 求 SIZE 的公式:

公式1: size = (1 << log2Group) + nDelta (1<<log2Delta)*

公式2: log2Group = log2Delta + 2

公式2带入公式1可得:

​ **size = (1 << log2Delta + 2) + nDelta * (1<<log2Delta) **

进一步 整理简化 最终可得出:

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

由最终整理后的公式 size = (1<< log2Delta) * (nDelta + 4) 可知两个结论:

结论一: 每一组中每行的log2Delta是相同的, 而nDelta的范围是 [1,4] ,那么 nDelta +4 的取值范围就是 [5,8],也就是说同一组中的 size是按照 5,6,7,8 倍来增长的。 (除第一组以外)

结论二:每一组的最后一个的 nDelta + 4 = 4 + 4 = 8 => 1<<3,那么 size = 1<< log2Delta+3 。 也就是说除了第一组外, 其它每一组的最后一行 size = 1 << log2Delta

sizeIdx2sizeTab

维护16MB以内所有规格 index 和 size 关系的一张表

index size(虚) Unit
0 16
1 32
2 48
3 64
4 80
5 96
6 112
7 128
8 160
9 192
10 224
11 256
12 320
13 384
14 448
15 512
16 640
17 768
18 896
19 1024
20 1280
21 1536
22 1792
23 2048
24 2560
25 3072
26 3584
27 4096 4KB
28 5120
29 6144
30 7168
31 8192 8KB
32 10240 10KB
33 12288 12KB
34 14336 14KB
35 16384 16KB
36 20480 20KB
37 24576 24KB
38 28672 28KB
39 32768 32KB
40 40960 40KB
41 49152 48KB
42 57344 56KB
43 65536 64KB
44 81920 80KB
45 98304 96KB
46 114688 112KB
47 131072 128KB
48 163840 160KB
49 196608 192KB
50 229376 224KB
51 262144 256KB
52 327680 320KB
53 393216 384KB
54 458752 448KB
55 524288 512KB
56 655360 640KB
57 786432 768KB
58 917504 896KB
59 1048576 1.0MB
60 1310720 1.25MB
61 1572864 1.5MB
62 1835008 1.75MB
63 2097152 2MB
64 2621440 2.5MB
65 3145728 3MB
66 3670016 3.5MB
67 4194304 4MB
68 5242880 5MB
69 6291456 6MB
70 7340030 7MB
71 8388608 8MB
72 10485760 10MB
73 12582912 12MB
74 14680064 14MB
75 16777216 16MB

pageIdx2sizeTab

isMultiPageSize 字段为1的行,会组合成 这张表。 也就该表都是 页(8KB) 的倍数的 size规格。

pageIndex index(虚) isMultiPageSize(虚) num of page(虚) size Unit
0 31 1 1 8192 8KB
1 35 1 2 16384 16KB
2 37 1 3 24576 24KB
3 39 1 4 32768 32KB
4 40 1 5 40960 40KB
5 41 1 6 49152 48KB
6 42 1 7 57344 56KB
7 43 1 8 65536 64KB
8 44 1 10 81920 80KB
9 45 1 12 98304 96KB
10 46 1 14 114688 112KB
11 47 1 16 131072 128KB
12 48 1 20 163840 160KB
13 49 1 24 196608 192KB
14 50 1 28 229376 224KB
15 51 1 32 262144 256KB
16 52 1 40 327680 320KB
17 53 1 48 393216 384KB
18 54 1 56 458752 448KB
19 55 1 64 524288 512KB
20 56 1 80 655360 640KB
21 57 1 96 786432 768KB
22 58 1 112 917504 896KB
23 59 1 128 1048576 1.0MB
24 60 1 160 1310720 1.25MB
25 61 1 192 1572864 1.5MB
26 62 1 224 1835008 1.75MB
27 63 1 256 2097152 2MB
28 64 1 320 2621440 2.5MB
29 65 1 384 3145728 3MB
30 66 1 448 3670016 3.5MB
31 67 1 512 4194304 4MB
32 68 1 640 5242880 5MB
33 69 1 768 6291456 6MB
34 70 1 896 7340030 7MB
35 71 1 1024 8388608 8MB
36 72 1 1280 10485760 10MB
37 73 1 1536 12582912 12MB
38 74 1 1792 14680064 14MB
39 75 1 2048 16777216 16MB

size2idxTab

size 小于等于 4KB (4096) 的 规格, 做一个细致的划分, 以16B为递增 增量。

由于里面行数太多,这里不展示全部的了,大致如下图:

Netty-内存池源码三 (SizeClasses 前情预热)

由于图表的原因 导致篇幅太长,具体的【SizeClasses】源码分析会到下期文章分析。

上一篇:游戏是如何检测到有OD等调试工具的


下一篇:springboot打包问题:解决Maven项目pom文件中出现的错误:“Missing artifact com.oracle:ojdbc6:jar:12.1.0"