深入淺出Netty內(nèi)存管理 PoolSubpage

上一節(jié)中分析了如何在poolChunk中分配一塊大于pageSize的內(nèi)存,但在實(shí)際應(yīng)用中,存在很多分配小內(nèi)存的情況加袋,如果也占用一個(gè)page,明顯很浪費(fèi)抱既。針對(duì)這種情況职烧,Netty提供了PoolSubpage把poolChunk的一個(gè)page節(jié)點(diǎn)8k內(nèi)存劃分成更小的內(nèi)存段,通過對(duì)每個(gè)內(nèi)存段的標(biāo)記與清理標(biāo)記進(jìn)行內(nèi)存的分配與釋放防泵。

PoolSubpage
final class PoolSubpage<T> {
    // 當(dāng)前page在chunk中的id
    private final int memoryMapIdx; 
    // 當(dāng)前page在chunk.memory的偏移量
    private final int runOffset;    
    // page大小
    private final int pageSize;
    //通過對(duì)每一個(gè)二進(jìn)制位的標(biāo)記來修改一段內(nèi)存的占用狀態(tài)
    private final long[] bitmap; 
    
    PoolSubpage<T> prev;     
    PoolSubpage<T> next;

    boolean doNotDestroy;    
    // 該page切分后每一段的大小
    int elemSize;   
    // 該page包含的段數(shù)量
    private int maxNumElems;        
    private int bitmapLength;
    // 下一個(gè)可用的位置
    private int nextAvail;
    // 可用的段數(shù)量
    private int numAvail;       
    ...
}

假設(shè)目前需要申請(qǐng)大小為4096的內(nèi)存:

long allocate(int normCapacity) {
    if ((normCapacity & subpageOverflowMask) != 0) { // >= pageSize
        return allocateRun(normCapacity);
    } else {
        return allocateSubpage(normCapacity);
    }
}

因?yàn)?096<pageSize(8192)蚀之,所以采用allocateSubpage進(jìn)行內(nèi)存分配,具體實(shí)現(xiàn)如下:

private long allocateSubpage(int normCapacity) {
    // 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(normCapacity);
    synchronized (head) {
        int d = maxOrder; // subpages are only be allocated from pages i.e., leaves
        int id = allocateNode(d);
        if (id < 0) {
            return id;
        }

        final PoolSubpage<T>[] subpages = this.subpages;
        final int pageSize = this.pageSize;

        freeBytes -= pageSize;

        int subpageIdx = subpageIdx(id);
        PoolSubpage<T> subpage = subpages[subpageIdx];
        if (subpage == null) {
            subpage = new PoolSubpage<T>(head, this, id, runOffset(id), pageSize, normCapacity);
            subpages[subpageIdx] = subpage;
        } else {
            subpage.init(head, normCapacity);
        }
        return subpage.allocate();
    }
}

1捷泞、Arena負(fù)責(zé)管理PoolChunk和PoolSubpage足删;
2、allocateNode負(fù)責(zé)在二叉樹中找到匹配的節(jié)點(diǎn)锁右,和poolChunk不同的是失受,只匹配葉子節(jié)點(diǎn);
3咏瑟、poolChunk中維護(hù)了一個(gè)大小為2048的poolSubpage數(shù)組拂到,分別對(duì)應(yīng)二叉樹中2048個(gè)葉子節(jié)點(diǎn),假設(shè)本次分配到節(jié)點(diǎn)2048响蕴,則取出poolSubpage數(shù)組第一個(gè)元素subpage谆焊;
4、如果subpage為空浦夷,則進(jìn)行初始化辖试,并加入到poolSubpage數(shù)組;

subpage初始化實(shí)現(xiàn)如下:

PoolSubpage(PoolSubpage<T> head, 
    PoolChunk<T> chunk, 
    int memoryMapIdx, int runOffset, 
    int pageSize, elemSize) {

    this.chunk = chunk;
    this.memoryMapIdx = memoryMapIdx;
    this.runOffset = runOffset;
    this.pageSize = pageSize;
    bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64
    init(head, elemSize);
}

1劈狐、默認(rèn)初始化bitmap長度為8罐孝,這里解釋一下為什么只需要8個(gè)元素:其中分配內(nèi)存大小都是處理過的,最小為16肥缔,說明一個(gè)page可以分成8192/16 = 512個(gè)內(nèi)存段莲兢,一個(gè)long有64位,可以描述64個(gè)內(nèi)存段续膳,這樣只需要512/64 = 8個(gè)long就可以描述全部內(nèi)存段了改艇。
2、init根據(jù)當(dāng)前需要分配的內(nèi)存大小坟岔,確定需要多少個(gè)bitmap元素谒兄,實(shí)現(xiàn)如下:

void init(PoolSubpage<T> head, int elemSize) {
    doNotDestroy = true;
    this.elemSize = elemSize;
    if (elemSize != 0) {
        maxNumElems = numAvail = pageSize / elemSize;
        nextAvail = 0;
        bitmapLength = maxNumElems >>> 6;
        if ((maxNumElems & 63) != 0) {
            bitmapLength ++;
        }

        for (int i = 0; i < bitmapLength; i ++) {
            bitmap[i] = 0;
        }
    }
    addToPool(head);
}

下面通過分布申請(qǐng)4096和32大小的內(nèi)存,說明如何確定bitmapLength的值:

  1. 比如社付,當(dāng)前申請(qǐng)大小4096的內(nèi)存承疲,maxNumElems 和 numAvail 為2邻耕,說明一個(gè)page被拆分成2個(gè)內(nèi)存段,2 >>> 6 = 0燕鸽,且2 & 63 兄世!= 0,所以bitmapLength為1啊研,說明只需要一個(gè)long就可以描述2個(gè)內(nèi)存段狀態(tài)御滩。
  2. 如果當(dāng)前申請(qǐng)大小32的內(nèi)存,maxNumElems 和 numAvail 為 256悲伶,說明一個(gè)page被拆分成256個(gè)內(nèi)存段艾恼, 256>>> 6 = 4,說明需要4個(gè)long描述256個(gè)內(nèi)存段狀態(tài)麸锉。

下面看看subpage是如何進(jìn)行內(nèi)存分配的:

long allocate() {
    if (elemSize == 0) {
        return toHandle(0);
    }

    if (numAvail == 0 || !doNotDestroy) {
        return -1;
    }

    final int bitmapIdx = getNextAvail();
    int q = bitmapIdx >>> 6;
    int r = bitmapIdx & 63;
    assert (bitmap[q] >>> r & 1) == 0;
    bitmap[q] |= 1L << r;

    if (-- numAvail == 0) {
        removeFromPool();
    }

    return toHandle(bitmapIdx);
}

1钠绍、方法getNextAvail負(fù)責(zé)找到當(dāng)前page中可分配內(nèi)存段的bitmapIdx;
2花沉、q = bitmapIdx >>> 6柳爽,確定bitmap數(shù)組下標(biāo)為q的long數(shù),用來描述 bitmapIdx 內(nèi)存段的狀態(tài)碱屁;
3磷脯、bitmapIdx & 63將超出64的那一部分二進(jìn)制數(shù)抹掉,得到一個(gè)小于64的數(shù)r娩脾;
4赵誓、bitmap[q] |= 1L << r將對(duì)應(yīng)位置q設(shè)置為1;

如果以上描述不直觀的話柿赊,下面換一種方式解釋俩功,假設(shè)需要分配大小為128的內(nèi)存,這時(shí)page會(huì)拆分成64個(gè)內(nèi)存段碰声,需要1個(gè)long類型的數(shù)字描述這64個(gè)內(nèi)存段的狀態(tài)诡蜓,所以bitmap數(shù)組只會(huì)用到第一個(gè)元素。

狀態(tài)轉(zhuǎn)換

getNextAvail如何實(shí)現(xiàn)找到下一個(gè)可分配的內(nèi)存段胰挑?

private int getNextAvail() {
    int nextAvail = this.nextAvail;
    if (nextAvail >= 0) {
        this.nextAvail = -1;
        return nextAvail;
    }
    return findNextAvail();
}

1蔓罚、如果nextAvail大于等于0,說明nextAvail指向了下一個(gè)可分配的內(nèi)存段瞻颂,直接返回nextAvail值豺谈;
2、每次分配完成贡这,nextAvail被置為-1茬末,這時(shí)只能通過方法findNextAvail重新計(jì)算出下一個(gè)可分配的內(nèi)存段位置。

private int findNextAvail() {
    final long[] bitmap = this.bitmap;
    final int bitmapLength = this.bitmapLength;
    for (int i = 0; i < bitmapLength; i ++) {
        long bits = bitmap[i];
        if (~bits != 0) {
            return findNextAvail0(i, bits);
        }
    }
    return -1;
}

private int findNextAvail0(int i, long bits) {
    final int maxNumElems = this.maxNumElems;
    final int baseVal = i << 6;
    for (int j = 0; j < 64; j ++) {
        if ((bits & 1) == 0) {
            int val = baseVal | j;
            if (val < maxNumElems) {
                return val;
            } else {
                break;
            }
        }
        bits >>>= 1;
    }
    return -1;
}

1藕坯、~bits != 0說明這個(gè)long所描述的64個(gè)內(nèi)存段還有未分配的团南;
2、(bits & 1) == 0 用來判斷該位置是否未分配炼彪,否則bits又移一位吐根,從左到右遍歷值為0的位置;

至此辐马,subpage內(nèi)存段已經(jīng)分配完成拷橘。

END。
我是占小狼喜爷。
在魔都艱苦奮斗冗疮,白天是上班族,晚上是知識(shí)服務(wù)工作者檩帐。
如果讀完覺得有收獲的話术幔,記得關(guān)注和點(diǎn)贊哦。
非要打賞的話湃密,我也是不會(huì)拒絕的诅挑。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市泛源,隨后出現(xiàn)的幾起案子拔妥,更是在濱河造成了極大的恐慌,老刑警劉巖达箍,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件没龙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡缎玫,警方通過查閱死者的電腦和手機(jī)硬纤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碘梢,“玉大人咬摇,你說我怎么就攤上這事∩饭” “怎么了肛鹏?”我有些...
    開封第一講書人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長恩沛。 經(jīng)常有香客問我在扰,道長,這世上最難降的妖魔是什么雷客? 我笑而不...
    開封第一講書人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任芒珠,我火速辦了婚禮,結(jié)果婚禮上搅裙,老公的妹妹穿的比我還像新娘皱卓。我一直安慰自己裹芝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開白布娜汁。 她就那樣靜靜地躺著嫂易,像睡著了一般。 火紅的嫁衣襯著肌膚如雪掐禁。 梳的紋絲不亂的頭發(fā)上怜械,一...
    開封第一講書人閱讀 51,727評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音傅事,去河邊找鬼缕允。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蹭越,可吹牛的內(nèi)容都是我干的障本。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼响鹃,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼彼绷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起茴迁,我...
    開封第一講書人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤寄悯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后堕义,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猜旬,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年倦卖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了洒擦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡怕膛,死狀恐怖熟嫩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情褐捻,我是刑警寧澤掸茅,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站柠逞,受9級(jí)特大地震影響昧狮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜板壮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一逗鸣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦撒璧、人聲如沸透葛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽获洲。三九已至,卻和暖如春殿如,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背最爬。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來泰國打工涉馁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人爱致。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓烤送,卻偏偏與公主長得像,于是被迫代替她去往敵國和親糠悯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子帮坚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容