Categories
程式開發

Netty源碼解析– 內存對齊類SizeClasses


在學習Netty內存池之前,我們先了解一下Netty的內存對齊類SizeClasses,它為Netty內存池中的內存塊提供大小對齊,索引計算等服務方法。

源碼分析基於Netty 4.1.52

Netty內存池中每個內存塊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

LOG2SIZECLASS_GROUP = 2

#1 log2Group,log2Delta都是從LOG2_QUANTUM開始

ndeltaLimit為2^LOG2SIZECLASS_GROUP,即內存塊size以4個為一組進行分組

#2 初始化第0組

nDelta從0開始

sizeClass方法計算每個size大小

注意:第0組後log2Group增加LOG2SIZECLASS_GROUP,而log2Delta不變

#3 初始化後面的size

nDelta從1開始

每組log2Group+1,log2Delta+1

將log2Group=log2Delta+LOG2_SIZE_CLASS_GROUP代入計算公式中,得到

大小= 1 <<(log2Delta + LOG2_SIZE_CLASS_GROUP)+ nDelta *(1 << log2Delta)

大小=(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表格內存如下:

Netty源碼解析-- 內存對齊類SizeClasses 1

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的行取出組成另一個表格

Netty源碼解析-- 內存對齊類SizeClasses 2

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 > 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 &
(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

從sizeClasses方法可以看到,除了第0組,都滿足shift = log2Group - LOG2_QUANTUM - (LOG2_SIZE_CLASS_GROUP - 1)。

shift << LOG2_SIZE_CLASS_GROUP就是該組第一個內存塊大小的索引

#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

nDelta - 1就是mod

& deltaInverseMask,將申請內存大小最後log2Delta個bit位設置為0,可以理解為減去n

>> log2Delta,右移log2Delta個bit位,就是除以(1 << log2Delta),結果就是(nDelta - 1 + 2 ^ LOG2SIZECLASS_GROUP)

&(1 << LOG2_SIZE_CLASS_GROUP)-1,取最後的LOG2SIZECLASS_GROUP個位位的值,結果就是mod

size - 1,是為了申請內存等於內存塊size時避免分配到下一個內存塊size中,即n == (1 << log2Delta)的場景。

疑問:既然右移log2Delta個bit位,那為什麼前面要將log2Delta個bit位設置為0?

第0組由於log2Group等於log2Delta,代入計算公式如下

1 << log2Delta +(nDelta-1)*(1 << log2Delta)+ n,備註:0

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 |

如果您覺得本文不錯,歡迎關注我的微信公眾號,您的關注是我堅持的動力!

Netty源碼解析-- 內存對齊類SizeClasses 3