在內(nèi)存管理(jemalloc3) 這篇文章中,我們介紹了在Netty 4.1.45 版本之前使用的內(nèi)存分配 jemalloc3 算法。
我們也說(shuō)了
jemalloc3算法的一個(gè)重大缺陷,就是對(duì)于Small和Normal規(guī)格類(lèi)型,每個(gè)規(guī)格內(nèi)存塊相差都是一倍,這就可以導(dǎo)致50%的內(nèi)存浪費(fèi);例如我們申請(qǐng)513B大小,那么只能分配1KB規(guī)格的內(nèi)存塊。
一. 劃分內(nèi)存規(guī)格
1.1 與 jemalloc3 對(duì)比


對(duì)比上面兩圖, jemalloc4 內(nèi)存分配算法和 jemalloc3 內(nèi)存分配算法的確有很大不同啊。
jemalloc4 算法將內(nèi)存分為三種類(lèi)型:
| 內(nèi)存規(guī)格 | 描述 |
|---|---|
Small |
小規(guī)格內(nèi)存塊,容量從16B 到28KB 一共39 個(gè)內(nèi)存規(guī)格 |
Normal |
正常規(guī)格內(nèi)存塊,容量從32KB 到16MB 一共37 個(gè)內(nèi)存規(guī)格 |
Huge |
巨大內(nèi)存塊,不會(huì)放在內(nèi)存管理中,直接內(nèi)存中申請(qǐng) |
你會(huì)發(fā)現(xiàn)沒(méi)有了
Tiny類(lèi)型,而且每個(gè)規(guī)格內(nèi)存相隔多少,好像從圖中也看不到,只知道一共分為76個(gè)內(nèi)存規(guī)格。
1.2 每個(gè)規(guī)格的內(nèi)存大小
| index | log2Group | log2Delta | nDelta | isMultiPageSize | isSubPage | log2DeltaLookup | size | log2Size | pageIdxAndPages | size2idxTab |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 4 | 4 | 0 | 0 | 1 | 4 | 16 | 4(4) | 0 | |
| 1 | 4 | 4 | 1 | 0 | 1 | 4 | 32 | 5(5) | 1 | |
| 2 | 4 | 4 | 2 | 0 | 1 | 4 | 48 | 5(6) | 2 | |
| 3 | 4 | 4 | 3 | 0 | 1 | 4 | 64 | 6(6) | 3 | |
| 4 | 6 | 4 | 1 | 0 | 1 | 4 | 80 | 6(7) | 4 | |
| 5 | 6 | 4 | 2 | 0 | 1 | 4 | 96 | 6(7) | 5 | |
| 6 | 6 | 4 | 3 | 0 | 1 | 4 | 112 | 6(7) | 6 | |
| 7 | 6 | 4 | 4 | 0 | 1 | 4 | 128 | 7(7) | 7 | |
| 8 | 7 | 5 | 1 | 0 | 1 | 5 | 160 | 7(8) | 8->10 | |
| 9 | 7 | 5 | 2 | 0 | 1 | 5 | 192 | 7(8) | 10->12 | |
| 10 | 7 | 5 | 3 | 0 | 1 | 5 | 224 | 7(8) | 12->14 | |
| 11 | 7 | 5 | 4 | 0 | 1 | 5 | 256 | 8(8) | 14->16 | |
| 12 | 8 | 6 | 1 | 0 | 1 | 6 | 320 | 8(9) | 16->20 | |
| 13 | 8 | 6 | 2 | 0 | 1 | 6 | 384 | 8(9) | 20->24 | |
| 14 | 8 | 6 | 3 | 0 | 1 | 6 | 448 | 8(9) | 24->28 | |
| 15 | 8 | 6 | 4 | 0 | 1 | 6 | 512 | 9(9) | 28->32 | |
| 16 | 9 | 7 | 1 | 0 | 1 | 7 | 640 | 9(10) | 32->40 | |
| 17 | 9 | 7 | 2 | 0 | 1 | 7 | 768 | 9(10) | 40->48 | |
| 18 | 9 | 7 | 3 | 0 | 1 | 7 | 896 | 9(10) | 48->56 | |
| 19 | 9 | 7 | 4 | 0 | 1 | 7 | 1024 | 10(10) | 56->64 | |
| 20 | 10 | 8 | 1 | 0 | 1 | 8 | 1280 | 10(11) | 64->80 | |
| 21 | 10 | 8 | 2 | 0 | 1 | 8 | 1536 | 10(11) | 80->96 | |
| 22 | 10 | 8 | 3 | 0 | 1 | 8 | 1792 | 10(11) | 96->112 | |
| 23 | 10 | 8 | 4 | 0 | 1 | 8 | 2048 | 11(11) | 112->128 | |
| 24 | 11 | 9 | 1 | 0 | 1 | 9 | 2560 | 11(12) | 128->160 | |
| 25 | 11 | 9 | 2 | 0 | 1 | 9 | 3072 | 11(12) | 160->192 | |
| 26 | 11 | 9 | 3 | 0 | 1 | 9 | 3584 | 11(12) | 192->224 | |
| 27 | 11 | 9 | 4 | 0 | 1 | 9 | 4096 | 12(12) | 224->256 | |
| 28 | 12 | 10 | 1 | 0 | 1 | 0 | 5120 | 12(13) | 無(wú) | |
| 29 | 12 | 10 | 2 | 0 | 1 | 0 | 6144 | 12(13) | 無(wú) | |
| 30 | 12 | 10 | 3 | 0 | 1 | 0 | 7168 | 12(13) | 無(wú) | |
| 31 | 12 | 10 | 4 | 1 | 1 | 0 | 8192 | 13(13) | 0(1) | 無(wú) |
| 32 | 13 | 11 | 1 | 0 | 1 | 0 | 10240 | 13(14) | 無(wú) | |
| 33 | 13 | 11 | 2 | 0 | 1 | 0 | 12288 | 13(14) | 無(wú) | |
| 34 | 13 | 11 | 3 | 0 | 1 | 0 | 14336 | 13(14) | 無(wú) | |
| 35 | 13 | 11 | 4 | 1 | 1 | 0 | 16384 | 14(14) | 1(2) | 無(wú) |
| 36 | 14 | 12 | 1 | 0 | 1 | 0 | 20480 | 14(15) | 無(wú) | |
| 37 | 14 | 12 | 2 | 1 | 1 | 0 | 24576 | 14(15) | 2(3) | 無(wú) |
| 38 | 14 | 12 | 3 | 0 | 1 | 0 | 28672 | 14(15) | 無(wú) | |
| 39 | 14 | 12 | 4 | 1 | 0 | 0 | 32768 | 15(15) | 3(4) | 無(wú) |
| 40 | 15 | 13 | 1 | 1 | 0 | 0 | 40960 | 15(16) | 4(5) | 無(wú) |
| 41 | 15 | 13 | 2 | 1 | 0 | 0 | 49152 | 15(16) | 5(6) | 無(wú) |
| 42 | 15 | 13 | 3 | 1 | 0 | 0 | 57344 | 15(16) | 6(7) | 無(wú) |
| 43 | 15 | 13 | 4 | 1 | 0 | 0 | 65536 | 16(16) | 7(8->9) | 無(wú) |
| 44 | 16 | 14 | 1 | 1 | 0 | 0 | 81920 | 16(17) | 8(10->11) | 無(wú) |
| 45 | 16 | 14 | 2 | 1 | 0 | 0 | 98304 | 16(17) | 9(12->13) | 無(wú) |
| 46 | 16 | 14 | 3 | 1 | 0 | 0 | 114688 | 16(17) | 10(14->15) | 無(wú) |
| 47 | 16 | 14 | 4 | 1 | 0 | 0 | 131072 | 17(17) | 11(16->19) | 無(wú) |
| 48 | 17 | 15 | 1 | 1 | 0 | 0 | 163840 | 17(18) | 12(20->23) | 無(wú) |
| 49 | 17 | 15 | 2 | 1 | 0 | 0 | 196608 | 17(18) | 13(24->27) | 無(wú) |
| 50 | 17 | 15 | 3 | 1 | 0 | 0 | 229376 | 17(18) | 14(28->31) | 無(wú) |
| 51 | 17 | 15 | 4 | 1 | 0 | 0 | 262144 | 18(18) | 15(32->39) | 無(wú) |
| 52 | 18 | 16 | 1 | 1 | 0 | 0 | 327680 | 18(19) | 16(40->47) | 無(wú) |
| 53 | 18 | 16 | 2 | 1 | 0 | 0 | 393216 | 18(19) | 17(48->55) | 無(wú) |
| 54 | 18 | 16 | 3 | 1 | 0 | 0 | 458752 | 18(19) | 18(56->63) | 無(wú) |
| 55 | 18 | 16 | 4 | 1 | 0 | 0 | 524288 | 19(19) | 19(64->79) | 無(wú) |
| 56 | 19 | 17 | 1 | 1 | 0 | 0 | 655360 | 19(20) | 20(80->95) | 無(wú) |
| 57 | 19 | 17 | 2 | 1 | 0 | 0 | 786432 | 19(20) | 21(96->111) | 無(wú) |
| 58 | 19 | 17 | 3 | 1 | 0 | 0 | 917504 | 19(20) | 22(112->127) | 無(wú) |
| 59 | 19 | 17 | 4 | 1 | 0 | 0 | 1048576 | 20(20) | 23(128->159) | 無(wú) |
| 60 | 20 | 18 | 1 | 1 | 0 | 0 | 1310720 | 20(21) | 24(160->191) | 無(wú) |
| 61 | 20 | 18 | 2 | 1 | 0 | 0 | 1572864 | 20(21) | 25(192->223) | 無(wú) |
| 62 | 20 | 18 | 3 | 1 | 0 | 0 | 1835008 | 20(21) | 26(224->255) | 無(wú) |
| 63 | 20 | 18 | 4 | 1 | 0 | 0 | 2097152 | 21(21) | 27(256->319) | 無(wú) |
| 64 | 21 | 19 | 1 | 1 | 0 | 0 | 2621440 | 21(22) | 28(320->383) | 無(wú) |
| 65 | 21 | 19 | 2 | 1 | 0 | 0 | 3145728 | 21(22) | 29(384->447) | 無(wú) |
| 66 | 21 | 19 | 3 | 1 | 0 | 0 | 3670016 | 21(22) | 30(448->511) | 無(wú) |
| 67 | 21 | 19 | 4 | 1 | 0 | 0 | 4194304 | 22(22) | 31(512->639) | 無(wú) |
| 68 | 22 | 20 | 1 | 1 | 0 | 0 | 5242880 | 22(23) | 32(640->767) | 無(wú) |
| 69 | 22 | 20 | 2 | 1 | 0 | 0 | 6291456 | 22(23) | 33(768->895) | 無(wú) |
| 70 | 22 | 20 | 3 | 1 | 0 | 0 | 7340032 | 22(23) | 34(896->1023) | 無(wú) |
| 71 | 22 | 20 | 4 | 1 | 0 | 0 | 8388608 | 23(23) | 35(1024->1279) | 無(wú) |
| 72 | 23 | 21 | 1 | 1 | 0 | 0 | 10485760 | 23(24) | 36(1280->1535) | 無(wú) |
| 73 | 23 | 21 | 2 | 1 | 0 | 0 | 12582912 | 23(24) | 37(1536->1791) | 無(wú) |
| 74 | 23 | 21 | 3 | 1 | 0 | 0 | 14680064 | 23(24) | 38(1792->2047) | 無(wú) |
| 75 | 23 | 21 | 4 | 1 | 0 | 0 | 16777216 | 24(24) | 39(2048) | 無(wú) |
先解釋每個(gè)表頭的含義:
-
index: 內(nèi)存規(guī)格的索引,一共有76個(gè)內(nèi)存規(guī)格,因此索引就是0 -> 75。 -
log2Group: 每一個(gè)內(nèi)存規(guī)格組的基礎(chǔ)大小的log2對(duì)數(shù)。 -
log2Delta: 每一個(gè)內(nèi)存規(guī)格組組內(nèi)內(nèi)存增量的log2對(duì)數(shù)。 -
nDelta: 每一個(gè)內(nèi)存規(guī)格組增量的個(gè)數(shù)。 -
isMultiPageSize: 表示是不是pageSize的倍數(shù)。 -
isSubPage: 表示是不是isSubPage類(lèi)型,就是Small類(lèi)型。 -
log2DeltaLookup: 對(duì)于方便較小size快速查詢(xún)對(duì)應(yīng)規(guī)格大小的輔助值。 -
size: 表示這個(gè)內(nèi)存規(guī)格對(duì)應(yīng)的內(nèi)存大小。 -
log2Size: 表示這個(gè)內(nèi)存規(guī)格對(duì)應(yīng)內(nèi)存大小的log2對(duì)數(shù),括號(hào)里的數(shù)是能容納這個(gè)內(nèi)存大小最近的log2對(duì)數(shù)。例如
48, 它的log2對(duì)數(shù)是5,但是能容納48最近的log2對(duì)數(shù)卻是6。 -
pageIdxAndPages: 表示pages對(duì)應(yīng)的下標(biāo),括號(hào)里面是它的范圍。因?yàn)閮?nèi)存規(guī)格增量會(huì)越來(lái)越大,后面會(huì)出現(xiàn)兩個(gè)內(nèi)存規(guī)格差值是
pageSize的幾倍,所以里面就表示它的范圍。 -
size2idxTab: 是用來(lái)方便查找較小size快速查詢(xún)對(duì)應(yīng)規(guī)格大小。
仔細(xì)觀察表中數(shù)據(jù),我們得出如下特點(diǎn):
- 每一組有
4種內(nèi)存規(guī)格,組內(nèi)每一種內(nèi)存規(guī)格的差值是一樣的,大小都是(1 << log2Delta)。 - 除了第一組外,每一組的
log2Group和log2Delta都比上一組增加了1。 - 除了第一組外,其他組的
log2Group == log2Delta + 2,第一組log2Group == log2Delta。 - 除了第一組外,其他組的
nDelta都是從1開(kāi)始的,第一組是從0開(kāi)始的。 - 除了第一組外,其他組第一個(gè)內(nèi)存規(guī)格正好是上一個(gè)組第一個(gè)內(nèi)存規(guī)格大小的兩倍。
我們知道內(nèi)存規(guī)格內(nèi)存大小的計(jì)算公式
size = 1 << log2Group + nDelta * (1 << log2Delta)
除第一組外,每一組的 nDelta 從1 開(kāi)始,log2Group == log2Delta + 2,并且每一組 log2Group 和 log2Delta 都比上一組增加了 1;因此每一組第一個(gè)內(nèi)存規(guī)格正好是上一個(gè)組第一個(gè)內(nèi)存規(guī)格大小的兩倍。
二. SizeClasses 類(lèi)
這個(gè)類(lèi)的作用就是處理 jemalloc4 算法的內(nèi)存大小分配的。
2.1 重要屬性
-
short[][] sizeClasses:是一個(gè)二維數(shù)組,一共是76個(gè) 長(zhǎng)度為7的short[]一維數(shù)組組成。short[7]就存儲(chǔ)上面表格前7標(biāo)頭數(shù)據(jù)。 -
int[] sizeIdx2sizeTab:用來(lái)記錄index對(duì)應(yīng)的內(nèi)存規(guī)格大小。數(shù)組長(zhǎng)度是
76,存儲(chǔ)了上面表格size的數(shù)據(jù),數(shù)組下標(biāo)就是index。 -
int[] pageIdx2sizeTab: 用來(lái)記錄pageIdxAndPages對(duì)應(yīng)的內(nèi)存規(guī)格大小。數(shù)組長(zhǎng)度是
40,存儲(chǔ)了上面表格size的數(shù)據(jù),數(shù)組下標(biāo)就是pageIdxAndPages。 -
int[] size2idxTab: 用來(lái)較小size對(duì)應(yīng)sizeIdx的值,這樣我們?cè)俨檎逸^小值對(duì)應(yīng)的內(nèi)存規(guī)格時(shí),可以直接通過(guò)這個(gè)數(shù)組得到。數(shù)組長(zhǎng)度是
256,就是上面表格size2idxTab相關(guān)值。
例如,我們求66對(duì)應(yīng)的內(nèi)存規(guī)格,(66 - 1)/16 = 3, 通過(guò)size2idxTab得到sizeIdx也是3;如果是257,(257 - 1)/16 = 16,通過(guò)size2idxTab得到sizeIdx也是11。
2.2 重要屬方法
public interface SizeClassesMetric {
/**
* 根據(jù)給定的數(shù)組索引 [sizeIdx] 從 [sizeIdx2sizeTab] 數(shù)組中獲取對(duì)應(yīng)的內(nèi)存大小
*
* @return size
*/
int sizeIdx2size(int sizeIdx);
/**
* 根據(jù)給定的數(shù)組索引 [sizeIdx],計(jì)算出對(duì)應(yīng)的內(nèi)存大小
*
* @return size
*/
int sizeIdx2sizeCompute(int sizeIdx);
/**
* 根據(jù)給定的數(shù)組索引 [pageIdx] 從 [pageIdx2sizeTab] 數(shù)組中獲取對(duì)應(yīng)的內(nèi)存大小
*
* @return size which is multiples of pageSize.
*/
long pageIdx2size(int pageIdx);
/**
* 根據(jù)給定的數(shù)組索引 [pageIdx],計(jì)算出對(duì)應(yīng)的內(nèi)存大小
*
* @return size which is multiples of pageSize
*/
long pageIdx2sizeCompute(int pageIdx);
/**
*
* 根據(jù)請(qǐng)求大小 [size] 得到規(guī)格化內(nèi)存的索引值[sizeIdx]
*
*/
int size2SizeIdx(int size);
/**
*
* 根據(jù)請(qǐng)求大小 [pages] 得到規(guī)格化內(nèi)存的索引值[pageIdx]
*
* @return pageIdx of the pageSize class
*/
int pages2pageIdx(int pages);
/**
* 根據(jù)請(qǐng)求大小 [pages] 得到規(guī)格化內(nèi)存的索引值[pageIdx],
* 與 pages2pageIdx(int pages) 方法不同,它會(huì)向下求得最近的[pageIdx]。
*
*/
int pages2pageIdxFloor(int pages);
/**
* 以指定的大小和對(duì)齊方式分配對(duì)象,使可用大小標(biāo)準(zhǔn)化。
*/
int normalizeSize(int size);
}
2.2.1 sizeIdx2size 方法
@Override
public int sizeIdx2size(int sizeIdx) {
return sizeIdx2sizeTab[sizeIdx];
}
2.2.2 sizeIdx2sizeCompute 方法
@Override
public int sizeIdx2sizeCompute(int sizeIdx) {
/**
* LOG2_SIZE_CLASS_GROUP = 2: 說(shuō)明每一組group的數(shù)量就是4個(gè);
* LOG2_QUANTUM = 4: 最小容量從16,即2的4次方開(kāi)始。
* 計(jì)算 size 的值,必須要區(qū)分第一組和后面的組,這個(gè)計(jì)算邏輯不同。
*/
// 因?yàn)槊恳唤M數(shù)量就是4, >> LOG2_SIZE_CLASS_GROUP 就是得到組group;
// 0 表示第一組,1就是第二組
int group = sizeIdx >> LOG2_SIZE_CLASS_GROUP;
// (1 << LOG2_SIZE_CLASS_GROUP) - 1 = 3;
// 和 sizeIdx 進(jìn)行位與(&) 運(yùn)算就是得到整除4的余數(shù)
int mod = sizeIdx & (1 << LOG2_SIZE_CLASS_GROUP) - 1;
/**
* 如果是第一組(group == 0),那么 groupSize就是0,最后size只通過(guò) modSize 得到;
* 其他組,那么組的基礎(chǔ)size: 1 << ( 5 + group), LOG2_QUANTUM + LOG2_SIZE_CLASS_GROUP - 1 的值就是 5,
* 這個(gè)計(jì)算公式就相當(dāng)于 1 << log2Group; log2Group = 5 + group
*/
int groupSize = group == 0? 0 :
1 << LOG2_QUANTUM + LOG2_SIZE_CLASS_GROUP - 1 << group;
// 第一組 shift == 1
// 其他組 shift = group,其實(shí)第二組也是 shift == group == 1
int shift = group == 0? 1 : group;
// lgDelta 就是 log2Delta 的值,第一組和第二組都是4,第三組是5
int lgDelta = shift + LOG2_QUANTUM - 1;
/**
* 除第一組外,其他組的 nDelta 都是從1開(kāi)始的,所以 nDelta = mod + 1;
* 第一組的 nDelta 從0開(kāi)始的,但是這里也使用了 mod + 1,那是因?yàn)槲覀冎苯訉?groupSize = 0
*/
int modSize = mod + 1 << lgDelta;
return groupSize + modSize;
}
2.2.3 pageIdx2size 方法
@Override
public long pageIdx2size(int pageIdx) {
return pageIdx2sizeTab[pageIdx];
}
2.2.4 pageIdx2sizeCompute 方法
@Override
public long pageIdx2sizeCompute(int pageIdx) {
/**
* 這個(gè)的邏輯和 sizeIdx2sizeCompute() 幾乎一模一樣,
* 唯一不同的是 sizeIdx2sizeCompute() 是從 LOG2_QUANTUM (4) 開(kāi)始
* 而這個(gè)方法是從 pageShifts (pageSize 13) 開(kāi)始
*/
int group = pageIdx >> LOG2_SIZE_CLASS_GROUP;
int mod = pageIdx & (1 << LOG2_SIZE_CLASS_GROUP) - 1;
long groupSize = group == 0? 0 :
1L << pageShifts + LOG2_SIZE_CLASS_GROUP - 1 << group;
int shift = group == 0? 1 : group;
int log2Delta = shift + pageShifts - 1;
int modSize = mod + 1 << log2Delta;
return groupSize + modSize;
}
2.2.5 size2SizeIdx 方法
/**
* 根據(jù)請(qǐng)求 size 得到對(duì)應(yīng)的 sizeIdx,
* 這個(gè) sizeIdx 就是 sizeIdx2sizeTab 數(shù)組的下標(biāo)。
* 其實(shí)就是得到這個(gè) size 對(duì)應(yīng)的規(guī)格化內(nèi)存塊大小
*/
@Override
public int size2SizeIdx(int size) {
if (size == 0) {
return 0;
}
// 如果大于 chunkSize,說(shuō)明它是一個(gè) Huge 類(lèi)型內(nèi)存塊;
// 那么返回 nSizes,超出 sizeIdx2sizeTab 數(shù)組的范圍。
if (size > chunkSize) {
return nSizes;
}
// 是否需要進(jìn)行內(nèi)存對(duì)齊,就是 size 必須是 directMemoryCacheAlignment 的倍數(shù)。
// directMemoryCacheAlignment 必須是 2 的冪數(shù),這樣才能進(jìn)行位運(yùn)算。
if (directMemoryCacheAlignment > 0) {
size = alignSize(size);
}
// 如果 size 小于等于 lookupMaxSize,
// 那么就在 size2idxTab 數(shù)組范圍內(nèi),
// 可以通過(guò) size2idxTab 數(shù)組快速得到 sizeIdx 值。
if (size <= lookupMaxSize) {
// >> LOG2_QUANTUM 相當(dāng)于 (size - 1) / 16,因?yàn)榭隙ǘ际?16 的倍數(shù)
// 獲取 sizeIdx 再通過(guò) sizeIdx2sizeTab 數(shù)組得到對(duì)應(yīng)大小
return size2idxTab[size - 1 >> LOG2_QUANTUM];
}
/**
* ((size << 1) - 1) 是向上得到 size 對(duì)應(yīng)的 log2 的數(shù);
* 例如 8 得到 3,9 得到 4,15 得到 4, 16 得到 4, 17 得到 5.
*
* LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1 的值就是 7。
* 而第一組規(guī)格內(nèi)存塊 size 最大就是 64, 計(jì)算后的值就是 6;
* 如果我們需要大小為 65,計(jì)算后的值就是 7, 在第二組的第一個(gè)規(guī)格內(nèi)存塊。
*
* 因此我們可以得出,
* 只要 x 小于 7,這個(gè) size 就是在第一組內(nèi);
* x 大于等于 7,這個(gè) size 在除第一組外的其他組內(nèi)。
*
* 第一組和其他組的算法不一樣
*/
int x = log2((size << 1) - 1);
// 判斷是第一組還是其他組。
// 第一組 shift 是 0,第二組是 1, 第三組是 2,依次遞增1
int shift = x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1
? 0 : x - (LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM);
// 因?yàn)槊恳唤M默認(rèn)是 4 個(gè),就是 shift << LOG2_SIZE_CLASS_GROUP,
// 得到這一組的開(kāi)始值。
int group = shift << LOG2_SIZE_CLASS_GROUP;
// 判斷是第一組還是其他組。
// 第一組就是 4, 第二組也是 4,第三組是5,依次遞增1
int log2Delta = x < LOG2_SIZE_CLASS_GROUP + LOG2_QUANTUM + 1
? LOG2_QUANTUM : x - LOG2_SIZE_CLASS_GROUP - 1;
int deltaInverseMask = -1 << log2Delta;
int mod = (size - 1 & deltaInverseMask) >> log2Delta &
(1 << LOG2_SIZE_CLASS_GROUP) - 1;
return group + mod;
}
2.2.6 pages2pageIdx 和 pages2pageIdxFloor 方法
@Override
public int pages2pageIdx(int pages) {
return pages2pageIdxCompute(pages, false);
}
@Override
public int pages2pageIdxFloor(int pages) {
return pages2pageIdxCompute(pages, true);
}
private int pages2pageIdxCompute(int pages, boolean floor) {
int pageSize = pages << pageShifts;
if (pageSize > chunkSize) {
return nPSizes;
}
int x = log2((pageSize << 1) - 1);
int shift = x < LOG2_SIZE_CLASS_GROUP + pageShifts
? 0 : x - (LOG2_SIZE_CLASS_GROUP + pageShifts);
int group = shift << LOG2_SIZE_CLASS_GROUP;
int log2Delta = x < LOG2_SIZE_CLASS_GROUP + pageShifts + 1?
pageShifts : x - LOG2_SIZE_CLASS_GROUP - 1;
int deltaInverseMask = -1 << log2Delta;
int mod = (pageSize - 1 & deltaInverseMask) >> log2Delta &
(1 << LOG2_SIZE_CLASS_GROUP) - 1;
int pageIdx = group + mod;
// floor 是true, 當(dāng) pageIdx 對(duì)應(yīng)的內(nèi)存大小比 pages 大時(shí),pageIdx要減一
if (floor && pageIdx2sizeTab[pageIdx] > pages << pageShifts) {
pageIdx--;
}
return pageIdx;
}
三. PoolChunk 方法
先理解重要概念:
-
page: 它是PoolChunk能分配內(nèi)存塊的最小單位。 -
run: 一個(gè)run中包含多個(gè)page;當(dāng)然也可以只包含一個(gè)page,就是單page的run。 -
chunk: 就是表示這個(gè)PoolChunk,包含多個(gè)run。
PoolChunk 進(jìn)行內(nèi)存塊的分配,就是分配出不同的 run,如下圖:
* /-----------------\
* | run |
* | |
* | |
* |-----------------|
* | run |
* | |
* |-----------------|
* | unalloctated |
* | (freed) |
* | |
* |-----------------|
* | subpage |
* |-----------------|
* | unallocated |
* | (freed) |
* | ... |
* | ... |
* | ... |
* | |
* | |
* | |
* \-----------------/
3.1 重要屬性
3.1.1 handle
那么怎么代表不同的 run 呢,就是用一個(gè) long 類(lèi)型的 handle 來(lái)表示:
oooooooo ooooooos ssssssss ssssssue bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb
handle 是 long 類(lèi)型,它的不同 bits 的意義如下:
-
o:前十五位bit表示這個(gè)內(nèi)存塊run的頁(yè)偏移量runOffset。因?yàn)?
PoolChunk是按照頁(yè)來(lái)進(jìn)行分割的,頁(yè)偏移量runOffset表示這個(gè)內(nèi)存塊run在這個(gè)PoolChunk第runOffset頁(yè)的位置。 -
s:中間十五位bit表示這個(gè)內(nèi)存塊run擁有的頁(yè)數(shù)size。 -
u:一位bit表示這個(gè)內(nèi)存塊run是否被使用。 -
e:一位bit表示這個(gè)內(nèi)存塊run是否是isSubpage。 -
b: 一共32位,表示subpage的bitmapIdx。
3.1.2 runsAvail
private final LongPriorityQueue[] runsAvail;
- 它是一個(gè)優(yōu)先級(jí)隊(duì)列
LongPriorityQueue的數(shù)組,數(shù)組長(zhǎng)度就是pageIdx數(shù)組的大小,默認(rèn)就是40。- 它管理者所有可分配的
run內(nèi)存塊。run根據(jù)擁有的頁(yè)數(shù)不同可以分為40中,所以runsAvail中每一個(gè)優(yōu)先級(jí)隊(duì)列管理一種類(lèi)型run內(nèi)存塊。LongPriorityQueue是一個(gè)優(yōu)先級(jí)隊(duì)列,利用堆排序來(lái)實(shí)現(xiàn)的,優(yōu)先級(jí)就是run內(nèi)存塊的頁(yè)偏移量runOffset。- 初始時(shí),
runsAvail在下標(biāo)39的優(yōu)先級(jí)隊(duì)列中存放這個(gè)整塊PoolChunk的run內(nèi)存塊,即runOffset是0,size是chunkSize/pageSize。
3.1.3 runsAvailMap
private final LongLongHashMap runsAvailMap;
- 將它看成一個(gè)
Map,存放著所有可分配的run內(nèi)存塊第一個(gè)和最后一個(gè)頁(yè)偏移量runOffset,以及run內(nèi)存塊的handle。runsAvailMap中的key就是頁(yè)偏移量runOffset,value就是內(nèi)存塊的handle。- 它作用就是當(dāng)釋放內(nèi)存塊
run的時(shí)候,可以將相連可分配合并在一起。
3.1.4 subpages
private final PoolSubpage<T>[] subpages
- 管理這個(gè)
PoolChunk下的所有PoolSubpage。- 特別注意
PoolSubpage的大小不固定是pageSize,有可能是幾個(gè)pageSize的大小。
3.2 分配內(nèi)存塊
3.2.1 allocate 方法
boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int sizeIdx, PoolThreadCache cache) {
final long handle;
if (sizeIdx <= arena.smallMaxSizeIdx) {
// small
handle = allocateSubpage(sizeIdx);
if (handle < 0) {
return false;
}
// 肯定是 isSubpage
assert isSubpage(handle);
} else {
// normal
// runSize must be multiple of pageSize
// 是 Normal 類(lèi)型,先根據(jù) sizeIdx 獲取對(duì)應(yīng)的規(guī)格內(nèi)存塊大小,肯定是 pageSize 的倍數(shù)
int runSize = arena.sizeIdx2size(sizeIdx);
handle = allocateRun(runSize);
if (handle < 0) {
return false;
}
}
ByteBuffer nioBuffer = cachedNioBuffers != null? cachedNioBuffers.pollLast() : null;
initBuf(buf, nioBuffer, handle, reqCapacity, cache);
return true;
}
- 如果是
Small規(guī)格類(lèi)型,那么就調(diào)用allocateSubpage(sizeIdx)方法,分配內(nèi)存塊。注意Small類(lèi)型最大是28KB,超過(guò)pageSize的大小。- 如果是
Normal規(guī)格類(lèi)型,那么就調(diào)用allocateRun(runSize)方法,分配內(nèi)存塊。
3.2.2 allocateRun 方法
private long allocateRun(int runSize) {
int pages = runSize >> pageShifts;
// 根據(jù)需要的 pages 得到對(duì)應(yīng)的 pageIdx,
// 也就是 run 的規(guī)格
int pageIdx = arena.pages2pageIdx(pages);
synchronized (runsAvail) {
//find first queue which has at least one big enough run
// 尋找第一個(gè)足夠大能分配需求 run 規(guī)格的內(nèi)存塊
int queueIdx = runFirstBestFit(pageIdx);
if (queueIdx == -1) {
// 表示當(dāng)前 PoolChunk 已經(jīng)沒(méi)有足夠的空間分配這個(gè) runSize
return -1;
}
//get run with min offset in this queue
LongPriorityQueue queue = runsAvail[queueIdx];
// 得到這個(gè)能分配的內(nèi)存塊,這里其實(shí)已經(jīng)從 queue 中刪除了這個(gè)內(nèi)存塊了
long handle = queue.poll();
// 這個(gè)內(nèi)存塊肯定是有效的
assert handle != LongPriorityQueue.NO_VALUE && !isUsed(handle) : "invalid handle: " + handle;
// 從隊(duì)列中移除這個(gè)可用內(nèi)存塊
removeAvailRun(queue, handle);
if (handle != -1) {
// 將這個(gè)可用內(nèi)存塊分割成兩部分:
// 一塊是 pages 大小,返回給調(diào)用者使用。
// 一塊是剩余大小,表示還能分配內(nèi)存塊,還需要存入 runsAvail 中
handle = splitLargeRun(handle, pages);
}
// runSize(pageShifts, handle) 表示返回內(nèi)存塊的大小
// 當(dāng)前 PoolChunk 的可用字節(jié)數(shù)要減少
freeBytes -= runSize(pageShifts, handle);
return handle;
}
}
- 從
runsAvail中尋找足夠大能分配需求可分配run內(nèi)存塊。- 調(diào)用
splitLargeRun(handle, pages)將可用內(nèi)存塊分割成兩部分,一塊是pages大小,返回給調(diào)用者使用;一塊是剩余大小,表示還能分配內(nèi)存塊,還需要存入runsAvail中。
3.2.3 allocateSubpage 方法
private long allocateSubpage(int sizeIdx) {
// Obtain the head of the PoolSubPage pool that is owned by the PoolArena and synchronize on it.
// This is need as we may add it back and so alter the linked-list structure.
PoolSubpage<T> head = arena.findSubpagePoolHead(sizeIdx);
synchronized (head) {
//allocate a new run
// 計(jì)算得到的值 runSize 必須是 pageSize 的倍數(shù),
int runSize = calculateRunSize(sizeIdx);
//runSize must be multiples of pageSize
// runSize 必須是 pageSize 的倍數(shù),
// 因?yàn)榉峙淇捎脙?nèi)存塊 run 就是按照幾個(gè) pageSize 分配的
long runHandle = allocateRun(runSize);
if (runHandle < 0) {
return -1;
}
int runOffset = runOffset(runHandle);
assert subpages[runOffset] == null;
int elemSize = arena.sizeIdx2size(sizeIdx);
PoolSubpage<T> subpage = new PoolSubpage<T>(head, this, pageShifts, runOffset,
runSize(pageShifts, runHandle), elemSize);
subpages[runOffset] = subpage;
// 返回 handle 是isSubpage, 且包含 bitmapIdx
return subpage.allocate();
}
}
- 調(diào)用
calculateRunSize(sizeIdx)方法,計(jì)算出即能整除需要的Small類(lèi)型內(nèi)存大小,又能整除pageSize的最小值。因?yàn)槲覀兎峙?run內(nèi)存塊大小只能是pageSize的倍數(shù)。- 調(diào)用
allocateRun(runSize)方法分配run內(nèi)存塊。- 創(chuàng)建
PoolSubpage對(duì)象,分配需求的Small類(lèi)型內(nèi)存塊。
3.2.3 initBuf 方法
void initBuf(PooledByteBuf<T> buf, ByteBuffer nioBuffer, long handle, int reqCapacity,
PoolThreadCache threadCache) {
if (isRun(handle)) {
// 如果是 Normal 規(guī)格類(lèi)型
// 真實(shí)偏移量就是 handle 的 (runOffset * pageSize)
// 大小就是 handle 的 (runSize * pageSize)
buf.init(this, nioBuffer, handle, runOffset(handle) << pageShifts,
reqCapacity, runSize(pageShifts, handle), arena.parent.threadCache());
} else {
initBufWithSubpage(buf, nioBuffer, handle, reqCapacity, threadCache);
}
}
void initBufWithSubpage(PooledByteBuf<T> buf, ByteBuffer nioBuffer, long handle, int reqCapacity,
PoolThreadCache threadCache) {
int runOffset = runOffset(handle);
int bitmapIdx = bitmapIdx(handle);
PoolSubpage<T> s = subpages[runOffset];
assert s.doNotDestroy;
assert reqCapacity <= s.elemSize;
// 真實(shí)偏移量就是handle 的 ((runOffset * pageSize) + (bitmapIdx * s.elemSize))
// 大小就是 elemSize
int offset = (runOffset << pageShifts) + bitmapIdx * s.elemSize;
buf.init(this, nioBuffer, handle, offset, reqCapacity, s.elemSize, threadCache);
}
初始化池緩沖區(qū)
PooledByteBuf, 只需要計(jì)算出偏移量和大小就可以了。
3.2.4 runFirstBestFit
/**
* 根據(jù) pageIdx 尋找第一個(gè)足夠大規(guī)格的 run
*/
private int runFirstBestFit(int pageIdx) {
if (freeBytes == chunkSize) {
// 如果這個(gè) PoolChunk 還沒(méi)有進(jìn)行分配,直接
return arena.nPSizes - 1;
}
// 從剛滿足run規(guī)格的 pageIdx 開(kāi)始,一直遍歷到最大run 規(guī)格(arena.nPSizes -1)
// 從 PoolChunk 中尋找能分配的 run
for (int i = pageIdx; i < arena.nPSizes; i++) {
LongPriorityQueue queue = runsAvail[i];
if (queue != null && !queue.isEmpty()) {
return i;
}
}
return -1;
}
3.2.5 splitLargeRun
private long splitLargeRun(long handle, int needPages) {
assert needPages > 0;
// 獲取這個(gè)可用內(nèi)存塊 run 一共有多少個(gè) pages
int totalPages = runPages(handle);
assert needPages <= totalPages;
// 還剩余的內(nèi)存塊大小
int remPages = totalPages - needPages;
if (remPages > 0) {
// 得到內(nèi)存塊 run 的偏移量
int runOffset = runOffset(handle);
// keep track of trailing unused pages for later use
// runOffset + needPages 表示剩余可用內(nèi)存塊的偏移量
int availOffset = runOffset + needPages;
// 得到新的剩余可用的內(nèi)存塊 run 的handle
long availRun = toRunHandle(availOffset, remPages, 0);
// 將剩余可用的內(nèi)存塊 run 存到 runsAvail 中
insertAvailRun(availOffset, remPages, availRun);
// 返回給調(diào)用者的內(nèi)存塊 run 的handle
return toRunHandle(runOffset, needPages, 1);
}
//mark it as used
// 將這個(gè) handle 變成已使用
handle |= 1L << IS_USED_SHIFT;
return handle;
}
3.2.6 calculateRunSize
/**
* 計(jì)算得到 runSize 必須是 pageSize 的倍數(shù)
*/
private int calculateRunSize(int sizeIdx) {
int maxElements = 1 << pageShifts - SizeClasses.LOG2_QUANTUM; // 512
int runSize = 0;
int nElements;
// 得到 sizeIdx 對(duì)應(yīng)規(guī)格化內(nèi)存塊的大小
final int elemSize = arena.sizeIdx2size(sizeIdx);
//find lowest common multiple of pageSize and elemSize
// 就是找到最小 pageSize 的倍數(shù) runSize,能夠整除 elemSize
// runSize != (runSize / elemSize) * elemSize 就表示 runSize 能夠整除 elemSize
do {
runSize += pageSize;
nElements = runSize / elemSize;
} while (nElements < maxElements && runSize != nElements * elemSize);
while (nElements > maxElements) {
runSize -= pageSize;
nElements = runSize / elemSize;
}
assert nElements > 0;
assert runSize <= chunkSize;
assert runSize >= elemSize;
return runSize;
}
3.3 釋放內(nèi)存塊
3.3.1 free
void free(long handle, int normCapacity, ByteBuffer nioBuffer) {
if (isSubpage(handle)) {
// 如果是 small 類(lèi)型
int sizeIdx = arena.size2SizeIdx(normCapacity);
// 找到 PoolArena 中這個(gè)small 規(guī)格類(lèi)型對(duì)應(yīng) PoolSubpage 鏈表
PoolSubpage<T> head = arena.findSubpagePoolHead(sizeIdx);
// 從 handle 中得到這個(gè)內(nèi)存塊 run 的偏移量 runOffset
int sIdx = runOffset(handle);
// 通過(guò)這個(gè)偏移量 runOffset 得到對(duì)應(yīng)的 PoolSubpage
PoolSubpage<T> subpage = subpages[sIdx];
assert subpage != null && subpage.doNotDestroy;
// Obtain the head of the PoolSubPage pool that is owned by the PoolArena and synchronize on it.
// This is need as we may add it back and so alter the linked-list structure.
synchronized (head) {
if (subpage.free(head, bitmapIdx(handle))) {
//the subpage is still used, do not free it
// 這個(gè) PoolSubpage 還在使用,不能釋放這塊內(nèi)存塊 run
return;
}
assert !subpage.doNotDestroy;
// Null out slot in the array as it was freed and we should not use it anymore.
// 這里和之前不一樣,直接設(shè)置為null,的確不需要復(fù)用,沒(méi)有多大意義
subpages[sIdx] = null;
}
}
// 程序運(yùn)行到這里肯定需要是否這個(gè) handle 對(duì)應(yīng)的內(nèi)存塊 run
int pages = runPages(handle);
synchronized (runsAvail) {
// collapse continuous runs, successfully collapsed runs
// will be removed from runsAvail and runsAvailMap
// 在這個(gè)要釋放的內(nèi)存塊周?chē)粩鄧L試向前或者向后合并可用內(nèi)存塊,
// 組成一個(gè)大的內(nèi)存塊
long finalRun = collapseRuns(handle);
//set run as not used
// 設(shè)置成未使用
finalRun &= ~(1L << IS_USED_SHIFT);
//if it is a subpage, set it to run
// 設(shè)置成非 subpage
finalRun &= ~(1L << IS_SUBPAGE_SHIFT);
// 將這個(gè)可用內(nèi)存塊run 插入到 runsAvail 中
insertAvailRun(runOffset(finalRun), runPages(finalRun), finalRun);
// 增加當(dāng)前 PoolChunk 的可用字節(jié)數(shù)
// 注意,這里一定不能用 runPages(finalRun),
// 因?yàn)殡m然我們可能合并了一個(gè)大的可用內(nèi)存塊,但是被合并的內(nèi)存塊原來(lái)就是可用的。
freeBytes += pages << pageShifts;
}
if (nioBuffer != null && cachedNioBuffers != null &&
cachedNioBuffers.size() < PooledByteBufAllocator.DEFAULT_MAX_CACHED_BYTEBUFFERS_PER_CHUNK) {
cachedNioBuffers.offer(nioBuffer);
}
}
- 如果是
small類(lèi)型,那么調(diào)用subpage.free()方法進(jìn)行釋放。- 釋放
normal類(lèi)型或者small類(lèi)型已經(jīng)完成釋放了,那么就要釋放這個(gè)run內(nèi)存塊。- 調(diào)用
collapseRuns(handle)方法來(lái)合并可分配內(nèi)存塊。- 調(diào)用
insertAvailRun(...)方法將內(nèi)存塊插入到runsAvail中。
3.3.2 collapseRuns
/**
* 不斷向前和向后合并可用內(nèi)存塊
*/
private long collapseRuns(long handle) {
return collapseNext(collapsePast(handle));
}
/**
* 不斷向前嘗試合并可用內(nèi)存塊
*/
private long collapsePast(long handle) {
// 通過(guò)循環(huán),不斷向前嘗試合并
for (;;) {
// 得到當(dāng)前 handle 對(duì)應(yīng)內(nèi)存塊的偏移量 runOffset
int runOffset = runOffset(handle);
// 得到當(dāng)前 handle 對(duì)應(yīng)內(nèi)存塊的大小 runPages
int runPages = runPages(handle);
/**
* 判斷釋放內(nèi)存塊 run 的前面有沒(méi)有可用內(nèi)存塊
*/
long pastRun = getAvailRunByOffset(runOffset - 1);
if (pastRun == -1) {
// 前面沒(méi)有可用內(nèi)存塊,就直接返回
return handle;
}
// 前面可用內(nèi)存塊 run 的偏移量pastOffset
int pastOffset = runOffset(pastRun);
// 前面可用內(nèi)存塊 run 的大小 pastPages
int pastPages = runPages(pastRun);
//is continuous
if (pastRun != handle && pastOffset + pastPages == runOffset) {
// 如果前一個(gè)可用內(nèi)存塊 run (pastOffset + pastPages == runOffset),
// 說(shuō)明前一個(gè)可用內(nèi)存塊和當(dāng)前釋放的內(nèi)存塊是連續(xù)的,就需要合并。
// 刪除前一個(gè)可用內(nèi)存塊,因?yàn)樗缓喜? removeAvailRun(pastRun);
// 創(chuàng)建新的包含前一個(gè)可用內(nèi)存塊的新合并內(nèi)存塊。
handle = toRunHandle(pastOffset, pastPages + runPages, 0);
} else {
return handle;
}
}
}
/**
* 不斷向后嘗試合并可用內(nèi)存塊
*/
private long collapseNext(long handle) {
// 通過(guò)循環(huán),不斷向后嘗試合并
for (;;) {
int runOffset = runOffset(handle);
int runPages = runPages(handle);
/**
* 判斷釋放內(nèi)存塊 run 的后面有沒(méi)有可用內(nèi)存塊
*/
long nextRun = getAvailRunByOffset(runOffset + runPages);
if (nextRun == -1) {
return handle;
}
int nextOffset = runOffset(nextRun);
int nextPages = runPages(nextRun);
//is continuous
if (nextRun != handle && runOffset + runPages == nextOffset) {
// 如果后一個(gè)可用內(nèi)存塊 run (runOffset + runPages == nextOffset),
// 說(shuō)明后一個(gè)可用內(nèi)存塊和當(dāng)前釋放的內(nèi)存塊是連續(xù)的,就需要合并。
// 刪除后一個(gè)可用內(nèi)存塊,因?yàn)樗缓喜? removeAvailRun(nextRun);
handle = toRunHandle(runOffset, runPages + nextPages, 0);
} else {
return handle;
}
}
}
就是查看要釋放的內(nèi)存塊,前后有沒(méi)有連續(xù)的可分配內(nèi)存塊,有的話,就合并成一個(gè)大的內(nèi)存塊。