共计 7210 个字符,预计需要花费 19 分钟才能阅读完成。
在学习 Netty 内存池之前,咱们先理解一下 Netty 的内存对齐类 SizeClasses,它为 Netty 内存池中的内存块提供大小对齐,索引计算等服务办法。
源码剖析基于 Netty 4.1.52
Netty 内存池中每个内存块 size 都合乎如下计算公式size = 1 << log2Group + nDelta * (1 << log2Delta)
log2Group:内存块分组
nDelta:增量乘数
log2Delta:增量大小的 log2 值
SizeClasses 初始化后,将计算 chunkSize(内存池每次向操作系统申请内存块大小)范畴内每个 size 的值,保留到 sizeClasses 字段中。
sizeClasses 是一个表格(二维数组),共有 7 列,含意如下
index: 内存块 size 的索引
log2Group: 内存块分组,用于计算对应的 size
log2Delata: 增量大小的 log2 值,用于计算对应的 size
nDelta: 增量乘数,用于计算对应的 size
isMultipageSize: 示意 size 是否为 page 的倍数
isSubPage: 示意是否为一个 subPage 类型
log2DeltaLookup: 如果 size 存在位图中的,记录其 log2Delta,未应用
sizeClasses 负责计算 sizeClasses 表格
private int sizeClasses() {
int normalMaxSize = -1;
int index = 0;
int size = 0;
// #1
int log2Group = LOG2_QUANTUM;
int log2Delta = LOG2_QUANTUM;
int ndeltaLimit = 1 << LOG2_SIZE_CLASS_GROUP;
// #2
int nDelta = 0;
while (nDelta < ndeltaLimit) {size = sizeClass(index++, log2Group, log2Delta, nDelta++);
}
log2Group += LOG2_SIZE_CLASS_GROUP;
// #3
while (size < chunkSize) {
nDelta = 1;
while (nDelta <= ndeltaLimit && size < chunkSize) {size = sizeClass(index++, log2Group, log2Delta, nDelta++);
normalMaxSize = size;
}
log2Group++;
log2Delta++;
}
//chunkSize must be normalMaxSize
assert chunkSize == normalMaxSize;
//return number of size index
return index;
}
LOG2_QUANTUM=4
LOG2_SIZE_CLASS_GROUP=2#1
log2Group,log2Delta 都是从 LOG2_QUANTUM 开始
ndeltaLimit 为 2^LOG2_SIZE_CLASS_GROUP,即内存块 size 以 4 个为一组进行分组#2
初始化第 0 组
nDelta 从 0 开始
sizeClass 办法计算每个 size 大小
留神:第 0 组后 log2Group 减少 LOG2_SIZE_CLASS_GROUP,而 log2Delta 不变 #3
初始化前面的 size
nDelta 从 1 开始
每组 log2Group+1,log2Delta+1
将 log2Group=log2Delta+LOG2_SIZE_CLASS_GROUP
代入计算公式中,失去size = 1 << (log2Delta+LOG2_SIZE_CLASS_GROUP) + nDelta * (1 << log2Delta)
size = (nDelta + 2 ^ LOG2_SIZE_CLASS_GROUP) * (1 << log2Delta)
能够看到,每个内存块 size 都是 (1 << log2Delta) 的倍数
从第二组开始,每组内这个倍数顺次是 5,6,7,8
每组内相邻行大小增量为 (1 << log2Delta),相邻组之间(1 << log2Delta) 翻倍。
Netty 默认的配置一个 page 的大小是 2^13, 即为 1KB, 默认的一个 chunk 的大小为 16777216,即 2MB。sizeClasses 表格内存如下:
size 并不是 sizeClasses 表格的列,这里为了直观而列出。
Netty 内存池中治理了大小不同的内存块,对于这些不同大小的内存块,Netty 划分为不同的等级 Small,Normal,Huge
sizeClasses 表格能够分为两局部
- isSubPage 为 1 的 size 为 Small 内存块,其余为 Normal 内存块,而大于 chunkSize 的为 huge 内存块,不在表格中。
调配 Small 内存块,须要找到对应的 index
通过 size2SizeIdx 办法计算 index
比方须要调配一个 360 位的内存块,须要从 sizeClasses 表格找到第一个大于 360 的内存块 size,即 384,其 index 为 13
- Normal 内存块必须是 page 的倍数。
将 isMultipageSize 为 1 的行取出组成另一个表格
pageIdx 并不是 sizeClasses 表格的列,它是这个新表格的索引。
PoolChunk 中调配 Normal 内存块需要查问对应的 pageIdx。
比方要调配一个 50000 位的内存块,须要从这个新表格找到第一个大于 50000 的内存块 size,即 57344,其 pageIdx 为 6
通过 pages2pageIdxCompute 办法计算 pageIdx。
上面看一下具体的计算方法
public int size2SizeIdx(int size) {if (size == 0) {return 0;}
// #1
if (size > chunkSize) {return nSizes;}
// #2
if (directMemoryCacheAlignment > 0) {size = alignSize(size);
}
// #3
if (size <= lookupMaxSize) {
//size-1 / MIN_TINY
return size2idxTab[size - 1 >> LOG2_QUANTUM];
}
// #4
int x = log2((size << 1) - 1);
// #5
int shift = x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1
? 0 : x - (LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM);
int group = shift << LOG2_SIZE_CLASS_GROUP;
// #6
int log2Delta = x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1
? LOG2_QUANTUM : x - LOG2_SIZE_CLASS_GROUP - 1;
// #7
int deltaInverseMask = -1 << log2Delta;
int mod = (size - 1 & deltaInverseMask) >> log2Delta &
(1 << LOG2_SIZE_CLASS_GROUP) - 1;
return group + mod;
}
#1
大于 chunkSize,就是返回 nSizes 代表申请的是 Huge 内存块。#2
不应用 sizeClasses 表格,将申请内存大小转换为 directMemoryCacheAlignment 的倍数,directMemoryCacheAlignment 默认为 0。#3
SizeClasses 将一部分较小的 size 与对应 index 记录在 size2idxTab 作为位图,这里间接查问 size2idxTab,防止反复计算
size2idxTab 中保留了(size-1)/(2^LOG2_QUANTUM) –> idx 的对应关系。#4
对申请内存大小进行 log2 的向上取整,就是每组最初一个内存块 size。- 1 是为了防止申请内存大小刚好等于 2 的指数次幂时被翻倍。
将log2Group = log2Delta + LOG2_SIZE_CLASS_GROUP
,nDelta=2^LOG2_SIZE_CLASS_GROUP
代入计算公式,可得 lastSize = 1 << (log2Group + 1)
即x = log2Group + 1
#5
shift,以后在第几组,从 0 开始(sizeClasses 表格中 0~3 行为第 0 组,4~7 行为第 1 组,以此类推,不是 log2Group)x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1
,即 log2Group < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM
,满足该条件的是第 0 组的 size,这时 shift 固定是 0。
从 sizeClasses 办法能够看到,除了第 0 组,都满足 shift = log2Group - LOG2_QUANTUM - (LOG2_SIZE_CLASS_GROUP - 1)
。shift << LOG2_SIZE_CLASS_GROUP
就是该组第一个内存块 size 的索引
#6
计算 log2Delta
第 0 组固定是 LOG2_QUANTUM
除了第 0 组,将 nDelta = 2^LOG2_SIZE_CLASS_GROUP
代入计算公式lastSize = (2^LOG2_SIZE_CLASS_GROUP + 2^LOG2_SIZE_CLASS_GROUP) * (1 << log2Delta)
lastSize = (1 << log2Delta) << LOG2_SIZE_CLASS_GROUP << 1
#7
后面曾经定位到第几组了,上面要找到申请内存大小应调配在该组第几位
这里要找到比申请内存大的最小 size。
申请内存大小能够了解为上一个 size 加上一个不大于 (1 << log2Delta) 的值,即(nDelta - 1 + 2^LOG2_SIZE_CLASS_GROUP) * (1 << log2Delta) + n
,备注:0 < n <= (1 << log2Delta)
nDelta – 1 就是 mod
& deltaInverseMask
,将申请内存大小最初 log2Delta 个 bit 位设置为 0,能够了解为减去 n >> log2Delta
,右移 log2Delta 个 bit 位,就是除以(1 << log2Delta),后果就是(nDelta – 1 + 2 ^ LOG2_SIZE_CLASS_GROUP)& (1 << LOG2_SIZE_CLASS_GROUP) - 1
,取最初的 LOG2_SIZE_CLASS_GROUP 个 bit 位的值,后果就是 mod
size - 1
,是为了申请内存等于内存块 size 时防止调配到下一个内存块 size 中,即 n == (1 << log2Delta)的场景。
疑难:既然右移 log2Delta 个 bit 位,那为什么后面要将 log2Delta 个 bit 位设置为 0?
第 0 组因为 log2Group 等于 log2Delta,代入计算公式如下 1 << log2Delta + (nDelta - 1) * (1 << log2Delta) + n
,备注:0 < n <= (1 << log2Delta)nDelta * (1 << log2Delta) + n
所以第 0 组 nDelta 从 0 开始,mod = nDelta
pages2pageIdxCompute 办法计算 pageIdx 逻辑与 size2SizeIdx 办法相似,只是将 LOG2_QUANTUM 变量换成了 pageShifts,这里不再反复。
SizeClasses 是给 PoolArena(内存池),PoolChunk(内存块)提供服务的,倡议大家联合前面剖析 PoolArena,PoolChunk 的文章一起了解。
如果大家对 SizeClasses 具体算法不感兴趣,只有了解 SizeClasses 类中利用 sizeClasses 表格,为 PoolArena,PoolChunk 提供计算 index,pageIdx 索引的办法,也能够帮忙大家了解前面解析 PoolArena,PoolChunk 的文章。
上面贴出 sizeClasses 残缺表格(可复制到 Excle,以 | 分列)
| index | log2Group | log2Delta | nDelta | isMultiPageSize | isSubPage | log2DeltaLookup | size |
| 0 | 4 | 4 | 0 | 0 | 1 | 4 | 16 -- 2B |
| 1 | 4 | 4 | 1 | 0 | 1 | 4 | 32 -- 4B |
| 2 | 4 | 4 | 2 | 0 | 1 | 4 | 48 -- 6B |
| 3 | 4 | 4 | 3 | 0 | 1 | 4 | 64 -- 8B |
| 4 | 6 | 4 | 1 | 0 | 1 | 4 | 80 -- 10B |
| 5 | 6 | 4 | 2 | 0 | 1 | 4 | 96 -- 12B |
| 6 | 6 | 4 | 3 | 0 | 1 | 4 | 112 -- 14B |
| 7 | 6 | 4 | 4 | 0 | 1 | 4 | 128 -- 16B |
| 8 | 7 | 5 | 1 | 0 | 1 | 5 | 160 -- 20B |
| 9 | 7 | 5 | 2 | 0 | 1 | 5 | 192 -- 24B |
| 10 | 7 | 5 | 3 | 0 | 1 | 5 | 224 -- 28B |
| 11 | 7 | 5 | 4 | 0 | 1 | 5 | 256 -- 32B |
| 12 | 8 | 6 | 1 | 0 | 1 | 6 | 320 -- 40B |
| 13 | 8 | 6 | 2 | 0 | 1 | 6 | 384 -- 48B |
| 14 | 8 | 6 | 3 | 0 | 1 | 6 | 448 -- 56B |
| 15 | 8 | 6 | 4 | 0 | 1 | 6 | 512 -- 64B |
| 16 | 9 | 7 | 1 | 0 | 1 | 7 | 640 -- 80B |
| 17 | 9 | 7 | 2 | 0 | 1 | 7 | 768 -- 96B |
| 18 | 9 | 7 | 3 | 0 | 1 | 7 | 896 -- 112B |
| 19 | 9 | 7 | 4 | 0 | 1 | 7 | 1024 -- 128B |
| 20 | 10 | 8 | 1 | 0 | 1 | 8 | 1280 -- 160B |
| 21 | 10 | 8 | 2 | 0 | 1 | 8 | 1536 -- 192B |
| 22 | 10 | 8 | 3 | 0 | 1 | 8 | 1792 -- 224B |
| 23 | 10 | 8 | 4 | 0 | 1 | 8 | 2048 -- 256B |
| 24 | 11 | 9 | 1 | 0 | 1 | 9 | 2560 -- 320B |
| 25 | 11 | 9 | 2 | 0 | 1 | 9 | 3072 -- 384B |
| 26 | 11 | 9 | 3 | 0 | 1 | 9 | 3584 -- 448B |
| 27 | 11 | 9 | 4 | 0 | 1 | 9 | 4096 -- 512B |
| 28 | 12 | 10 | 1 | 0 | 1 | 0 | 5120 -- 640B |
| 29 | 12 | 10 | 2 | 0 | 1 | 0 | 6144 -- 768B |
| 30 | 12 | 10 | 3 | 0 | 1 | 0 | 7168 -- 896B |
| 31 | 12 | 10 | 4 | 1 | 1 | 0 | 8192 -- 1.0KB |
| 32 | 13 | 11 | 1 | 0 | 1 | 0 | 10240 -- 1.25KB |
| 33 | 13 | 11 | 2 | 0 | 1 | 0 | 12288 -- 1.5KB |
| 34 | 13 | 11 | 3 | 0 | 1 | 0 | 14336 -- 1.75KB |
| 35 | 13 | 11 | 4 | 1 | 1 | 0 | 16384 -- 2.0KB |
| 36 | 14 | 12 | 1 | 0 | 1 | 0 | 20480 -- 2.5KB |
| 37 | 14 | 12 | 2 | 1 | 1 | 0 | 24576 -- 3.0KB |
| 38 | 14 | 12 | 3 | 0 | 1 | 0 | 28672 -- 3.5KB |
| 39 | 14 | 12 | 4 | 1 | 0 | 0 | 32768 -- 4.0KB |
| 40 | 15 | 13 | 1 | 1 | 0 | 0 | 40960 -- 5.0KB |
| 41 | 15 | 13 | 2 | 1 | 0 | 0 | 49152 -- 6.0KB |
| 42 | 15 | 13 | 3 | 1 | 0 | 0 | 57344 -- 7.0KB |
| 43 | 15 | 13 | 4 | 1 | 0 | 0 | 65536 -- 8.0KB |
| 44 | 16 | 14 | 1 | 1 | 0 | 0 | 81920 -- 10.0KB |
| 45 | 16 | 14 | 2 | 1 | 0 | 0 | 98304 -- 12.0KB |
| 46 | 16 | 14 | 3 | 1 | 0 | 0 | 114688 -- 14.0KB |
| 47 | 16 | 14 | 4 | 1 | 0 | 0 | 131072 -- 16.0KB |
| 48 | 17 | 15 | 1 | 1 | 0 | 0 | 163840 -- 20.0KB |
| 49 | 17 | 15 | 2 | 1 | 0 | 0 | 196608 -- 24.0KB |
| 50 | 17 | 15 | 3 | 1 | 0 | 0 | 229376 -- 28.0KB |
| 51 | 17 | 15 | 4 | 1 | 0 | 0 | 262144 -- 32.0KB |
| 52 | 18 | 16 | 1 | 1 | 0 | 0 | 327680 -- 40.0KB |
| 53 | 18 | 16 | 2 | 1 | 0 | 0 | 393216 -- 48.0KB |
| 54 | 18 | 16 | 3 | 1 | 0 | 0 | 458752 -- 56.0KB |
| 55 | 18 | 16 | 4 | 1 | 0 | 0 | 524288 -- 64.0KB |
| 56 | 19 | 17 | 1 | 1 | 0 | 0 | 655360 -- 80.0KB |
| 57 | 19 | 17 | 2 | 1 | 0 | 0 | 786432 -- 96.0KB |
| 58 | 19 | 17 | 3 | 1 | 0 | 0 | 917504 -- 112.0KB |
| 59 | 19 | 17 | 4 | 1 | 0 | 0 | 1048576 -- 128.0KB |
| 60 | 20 | 18 | 1 | 1 | 0 | 0 | 1310720 -- 160.0KB |
| 61 | 20 | 18 | 2 | 1 | 0 | 0 | 1572864 -- 192.0KB |
| 62 | 20 | 18 | 3 | 1 | 0 | 0 | 1835008 -- 224.0KB |
| 63 | 20 | 18 | 4 | 1 | 0 | 0 | 2097152 -- 256.0KB |
| 64 | 21 | 19 | 1 | 1 | 0 | 0 | 2621440 -- 320.0KB |
| 65 | 21 | 19 | 2 | 1 | 0 | 0 | 3145728 -- 384.0KB |
| 66 | 21 | 19 | 3 | 1 | 0 | 0 | 3670016 -- 448.0KB |
| 67 | 21 | 19 | 4 | 1 | 0 | 0 | 4194304 -- 512.0KB |
| 68 | 22 | 20 | 1 | 1 | 0 | 0 | 5242880 -- 640.0KB |
| 69 | 22 | 20 | 2 | 1 | 0 | 0 | 6291456 -- 768.0KB |
| 70 | 22 | 20 | 3 | 1 | 0 | 0 | 7340032 -- 896.0KB |
| 71 | 22 | 20 | 4 | 1 | 0 | 0 | 8388608 -- 1.0MB |
| 72 | 23 | 21 | 1 | 1 | 0 | 0 | 10485760 -- 1.25MB |
| 73 | 23 | 21 | 2 | 1 | 0 | 0 | 12582912 -- 1.5MB |
| 74 | 23 | 21 | 3 | 1 | 0 | 0 | 14680064 -- 1.75MB |
| 75 | 23 | 21 | 4 | 1 | 0 | 0 | 16777216 -- 2.0MB |
如果您感觉本文不错,欢送关注我的微信公众号,您的关注是我保持的能源!