Netty源碼_內(nèi)存管理(jemalloc4)

內(nèi)存管理(jemalloc3) 這篇文章中,我們介紹了在Netty 4.1.45 版本之前使用的內(nèi)存分配 jemalloc3 算法。

我們也說(shuō)了 jemalloc3 算法的一個(gè)重大缺陷,就是對(duì)于 SmallNormal 規(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ì)比

jemalloc3_內(nèi)存規(guī)格.png
jemalloc4_內(nèi)存規(guī)格.png

對(duì)比上面兩圖, jemalloc4 內(nèi)存分配算法和 jemalloc3 內(nèi)存分配算法的確有很大不同啊。
jemalloc4 算法將內(nèi)存分為三種類(lèi)型:

內(nèi)存規(guī)格 描述
Small 小規(guī)格內(nèi)存塊,容量從16B28KB 一共39 個(gè)內(nèi)存規(guī)格
Normal 正常規(guī)格內(nèi)存塊,容量從32KB16MB 一共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)。
  • 除了第一組外,每一組的 log2Grouplog2Delta 都比上一組增加了 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)

除第一組外,每一組的 nDelta1 開(kāi)始,log2Group == log2Delta + 2,并且每一組 log2Grouplog2Delta 都比上一組增加了 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)度為7short[] 一維數(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 pages2pageIdxpages2pageIdxFloor 方法

    @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,就是單pagerun
  • 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

handlelong 類(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è) PoolChunkrunOffset 頁(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位,表示subpagebitmapIdx

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è)整塊PoolChunkrun 內(nèi)存塊,即 runOffset0, sizechunkSize/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)存塊。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Netty 是一個(gè)高性能的網(wǎng)絡(luò)應(yīng)用程序框架,主要就是進(jìn)行數(shù)據(jù)的交互,所以必須有一個(gè)高效的內(nèi)存分配器。內(nèi)存分配器的功...
    wo883721閱讀 909評(píng)論 0 2
  • 內(nèi)存管理的主要目的合理分配內(nèi)存,減少內(nèi)存碎片,及時(shí)回收資源,提高內(nèi)存的使用效率。從操作系統(tǒng)層面來(lái)說(shuō),各個(gè)軟件在運(yùn)行...
    史圣杰閱讀 1,314評(píng)論 0 1
  • 背景 生活就像一座圍城,城外的人想進(jìn)去,城里的人想出去。寫(xiě)java程序的同學(xué)基本上不太用關(guān)心內(nèi)存分配算法實(shí)現(xiàn),也不...
    whateverblake閱讀 1,602評(píng)論 0 2
  • Netty作為一款高性能網(wǎng)絡(luò)應(yīng)用程序框架,實(shí)現(xiàn)了一套高性能內(nèi)存管理機(jī)制 通過(guò)學(xué)習(xí)其中的實(shí)現(xiàn)原理、算法、并發(fā)設(shè)計(jì),有...
    caison閱讀 1,292評(píng)論 2 4
  • 1、內(nèi)存管理介紹 內(nèi)存管理的目的是合理分配內(nèi)存,減少內(nèi)存碎片,及時(shí)回收資源,提高內(nèi)存的使用資源。 可以帶著以下問(wèn)題...
    技術(shù)滅霸閱讀 1,981評(píng)論 0 3

友情鏈接更多精彩內(nèi)容