Netty原始碼解析 -- 記憶體對齊類SizeClasses
阿新 • • 發佈:2020-11-22
在學習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方法計算sizeClasses每一行內容
注意:第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,即為8KB,預設的一個chunk的大小為16777216,即16MB。sizeClasses表格記憶體如下:
![](https://upload-images.jianshu.io/upload_images/3804367-e5041bb050a63038.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
Netty記憶體池中管理了大小不同的記憶體塊,對於這些不同大小的記憶體塊,Netty劃分為不同的等級Small,Normal,Huge。Huge是大於chunkSize的記憶體塊,不在表格中,這裡也不討論。
sizeClasses表格可以分為兩部分
1. isSubPage為1的size為Small記憶體塊,其他為Normal記憶體塊。
分配Small記憶體塊,需要找到對應的index
通過size2SizeIdx方法計算index
比如需要分配一個90位元組的記憶體塊,需要從sizeClasses表格找到第一個大於90的記憶體塊size,即96,其index為5。
2. Normal記憶體塊必須是page的倍數。
將isMultipageSize為1的行取出組成另一個表格
![](https://upload-images.jianshu.io/upload_images/3804367-6a24ca2e0ef59e31.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
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的對應關係。
從sizeClasses方法可以看到,sizeClasses表格中每個size都是(2^LOG2_QUANTUM) 的倍數。
`#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 | usize |
| 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 | 1K |
| 20 | 10 | 8 | 1 | 0 | 1 | 8 | 1280 | 1.25K |
| 21 | 10 | 8 | 2 | 0 | 1 | 8 | 1536 | 1.5K |
| 22 | 10 | 8 | 3 | 0 | 1 | 8 | 1792 | 1.75K |
| 23 | 10 | 8 | 4 | 0 | 1 | 8 | 2048 | 2K |
| 24 | 11 | 9 | 1 | 0 | 1 | 9 | 2560 | 2.5K |
| 25 | 11 | 9 | 2 | 0 | 1 | 9 | 3072 | 3K |
| 26 | 11 | 9 | 3 | 0 | 1 | 9 | 3584 | 3.5K |
| 27 | 11 | 9 | 4 | 0 | 1 | 9 | 4096 | 4K |
| 28 | 12 | 10 | 1 | 0 | 1 | 0 | 5120 | 5K |
| 29 | 12 | 10 | 2 | 0 | 1 | 0 | 6144 | 6K |
| 30 | 12 | 10 | 3 | 0 | 1 | 0 | 7168 | 7K |
| 31 | 12 | 10 | 4 | 1 | 1 | 0 | 8192 | 8K |
| 32 | 13 | 11 | 1 | 0 | 1 | 0 | 10240 | 10K |
| 33 | 13 | 11 | 2 | 0 | 1 | 0 | 12288 | 12K |
| 34 | 13 | 11 | 3 | 0 | 1 | 0 | 14336 | 14K |
| 35 | 13 | 11 | 4 | 1 | 1 | 0 | 16384 | 16K |
| 36 | 14 | 12 | 1 | 0 | 1 | 0 | 20480 | 20K |
| 37 | 14 | 12 | 2 | 1 | 1 | 0 | 24576 | 24K |
| 38 | 14 | 12 | 3 | 0 | 1 | 0 | 28672 | 28K |
| 39 | 14 | 12 | 4 | 1 | 0 | 0 | 32768 | 32K |
| 40 | 15 | 13 | 1 | 1 | 0 | 0 | 40960 | 40K |
| 41 | 15 | 13 | 2 | 1 | 0 | 0 | 49152 | 48K |
| 42 | 15 | 13 | 3 | 1 | 0 | 0 | 57344 | 56K |
| 43 | 15 | 13 | 4 | 1 | 0 | 0 | 65536 | 64K |
| 44 | 16 | 14 | 1 | 1 | 0 | 0 | 81920 | 80K |
| 45 | 16 | 14 | 2 | 1 | 0 | 0 | 98304 | 96K |
| 46 | 16 | 14 | 3 | 1 | 0 | 0 | 114688 | 112K |
| 47 | 16 | 14 | 4 | 1 | 0 | 0 | 131072 | 128K |
| 48 | 17 | 15 | 1 | 1 | 0 | 0 | 163840 | 160K |
| 49 | 17 | 15 | 2 | 1 | 0 | 0 | 196608 | 192K |
| 50 | 17 | 15 | 3 | 1 | 0 | 0 | 229376 | 224K |
| 51 | 17 | 15 | 4 | 1 | 0 | 0 | 262144 | 256K |
| 52 | 18 | 16 | 1 | 1 | 0 | 0 | 327680 | 320K |
| 53 | 18 | 16 | 2 | 1 | 0 | 0 | 393216 | 384K |
| 54 | 18 | 16 | 3 | 1 | 0 | 0 | 458752 | 448K |
| 55 | 18 | 16 | 4 | 1 | 0 | 0 | 524288 | 512K |
| 56 | 19 | 17 | 1 | 1 | 0 | 0 | 655360 | 640K |
| 57 | 19 | 17 | 2 | 1 | 0 | 0 | 786432 | 768K |
| 58 | 19 | 17 | 3 | 1 | 0 | 0 | 917504 | 896K |
| 59 | 19 | 17 | 4 | 1 | 0 | 0 | 1048576 | 1M |
| 60 | 20 | 18 | 1 | 1 | 0 | 0 | 1310720 | 1.25M |
| 61 | 20 | 18 | 2 | 1 | 0 | 0 | 1572864 | 1.5M |
| 62 | 20 | 18 | 3 | 1 | 0 | 0 | 1835008 | 1.75M |
| 63 | 20 | 18 | 4 | 1 | 0 | 0 | 2097152 | 2M |
| 64 | 21 | 19 | 1 | 1 | 0 | 0 | 2621440 | 2.5M |
| 65 | 21 | 19 | 2 | 1 | 0 | 0 | 3145728 | 3M |
| 66 | 21 | 19 | 3 | 1 | 0 | 0 | 3670016 | 3.5M |
| 67 | 21 | 19 | 4 | 1 | 0 | 0 | 4194304 | 4M |
| 68 | 22 | 20 | 1 | 1 | 0 | 0 | 5242880 | 5M |
| 69 | 22 | 20 | 2 | 1 | 0 | 0 | 6291456 | 6M |
| 70 | 22 | 20 | 3 | 1 | 0 | 0 | 7340032 | 7M |
| 71 | 22 | 20 | 4 | 1 | 0 | 0 | 8388608 | 8M |
| 72 | 23 | 21 | 1 | 1 | 0 | 0 | 10485760 | 10M |
| 73 | 23 | 21 | 2 | 1 | 0 | 0 | 12582912 | 12M |
| 74 | 23 | 21 | 3 | 1 | 0 | 0 | 14680064 | 14M |
| 75 | 23 | 21 | 4 | 1 | 0 | 0 | 16777216 | 16M |
```
如果您覺得本文不錯,歡迎關注我的微信公眾號,系列文章持續更新中。您的關注是我堅持的動力!
![](https://upload-images.jianshu.io/upload_images/3804367-a3375313d1db6207.png?imageMogr2/auto-orient/strip%7CimageView2/2/