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

多年之前即寡,從C內(nèi)存的手動(dòng)管理上升到j(luò)ava的自動(dòng)GC江醇,是歷史的巨大進(jìn)步告喊。然而多年之后闪檬,netty的內(nèi)存實(shí)現(xiàn)又曲線的回到了手動(dòng)管理模式晨抡,正印證了馬克思哲學(xué)觀:社會(huì)總是在螺旋式前進(jìn)的氛悬,沒(méi)有永遠(yuǎn)的最好。的確耘柱,就內(nèi)存管理而言如捅,GC給程序員帶來(lái)的價(jià)值是不言而喻的,不僅大大的降低了程序員的負(fù)擔(dān)调煎,而且也極大的減少了內(nèi)存管理帶來(lái)的Crash困擾镜遣,不過(guò)也有很多情況,可能手動(dòng)的內(nèi)存管理更為合適士袄。

接下去準(zhǔn)備幾個(gè)篇幅對(duì)Netty的內(nèi)存管理進(jìn)行深入分析悲关。

PoolChunk

為了能夠簡(jiǎn)單的操作內(nèi)存,必須保證每次分配到的內(nèi)存時(shí)連續(xù)的娄柳。Netty中底層的內(nèi)存分配和回收管理主要由PoolChunk實(shí)現(xiàn)寓辱,其內(nèi)部維護(hù)一棵平衡二叉樹memoryMap,所有子節(jié)點(diǎn)管理的內(nèi)存也屬于其父節(jié)點(diǎn)赤拒。

memoryMap

poolChunk默認(rèn)由2048個(gè)page組成秫筏,一個(gè)page默認(rèn)大小為8k,圖中節(jié)點(diǎn)的值為在數(shù)組memoryMap的下標(biāo)挎挖。
1这敬、如果需要分配大小8k的內(nèi)存,則只需要在第11層蕉朵,找到第一個(gè)可用節(jié)點(diǎn)即可崔涂。
2、如果需要分配大小16k的內(nèi)存始衅,則只需要在第10層冷蚂,找到第一個(gè)可用節(jié)點(diǎn)即可缭保。
3、如果節(jié)點(diǎn)1024存在一個(gè)已經(jīng)被分配的子節(jié)點(diǎn)2048帝雇,則該節(jié)點(diǎn)不能被分配涮俄,如需要分配大小16k的內(nèi)存,這個(gè)時(shí)候節(jié)點(diǎn)2048已被分配尸闸,節(jié)點(diǎn)2049未被分配彻亲,就不能直接分配節(jié)點(diǎn)1024,因?yàn)樵摴?jié)點(diǎn)目前只剩下8k內(nèi)存吮廉。

poolChunk內(nèi)部會(huì)保證每次分配內(nèi)存大小為8K*(2n)苞尝,為了分配一個(gè)大小為chunkSize/(2k)的節(jié)點(diǎn),需要在深度為k的層從左開始匹配節(jié)點(diǎn)宦芦,那么如何快速的分配到指定內(nèi)存宙址?

memoryMap初始化:

memoryMap = new byte[maxSubpageAllocs << 1];
depthMap = new byte[memoryMap.length];
int memoryMapIndex = 1;
for (int d = 0; d <= maxOrder; ++ d) { // move down the tree one level at a time
    int depth = 1 << d;
    for (int p = 0; p < depth; ++ p) {
        // in each level traverse left to right and set value to the depth of subtree
        memoryMap[memoryMapIndex] = (byte) d;
        depthMap[memoryMapIndex] = (byte) d;
        memoryMapIndex ++;
    }
}

memoryMap數(shù)組中每個(gè)位置保存的是該節(jié)點(diǎn)所在的層數(shù),有什么作用调卑?對(duì)于節(jié)點(diǎn)512抡砂,其層數(shù)是9,則:
1恬涧、如果memoryMap[512] = 9注益,則表示其本身到下面所有的子節(jié)點(diǎn)都可以被分配;
2溯捆、如果memoryMap[512] = 10丑搔, 則表示節(jié)點(diǎn)512下有子節(jié)點(diǎn)已經(jīng)分配過(guò),則該節(jié)點(diǎn)不能直接被分配提揍,而其子節(jié)點(diǎn)中的第10層還存在未分配的節(jié)點(diǎn);
3啤月、如果memoryMap[512] = 12 (即總層數(shù) + 1), 可分配的深度已經(jīng)大于總層數(shù), 則表示該節(jié)點(diǎn)下的所有子節(jié)點(diǎn)都已經(jīng)被分配。

下面看看如何向PoolChunk申請(qǐng)一段內(nèi)存:

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

1劳跃、當(dāng)需要分配的內(nèi)存大于pageSize時(shí)谎仲,使用allocateRun實(shí)現(xiàn)內(nèi)存分配。
2刨仑、否則使用方法allocateSubpage分配內(nèi)存强重,在allocateSubpage實(shí)現(xiàn)中,會(huì)把一個(gè)page分割成多段贸人,進(jìn)行內(nèi)存分配。

這里先看看allocateRun是如何實(shí)現(xiàn)的:

private long allocateRun(int normCapacity) {
    int d = maxOrder - (log2(normCapacity) - pageShifts);
    int id = allocateNode(d);
    if (id < 0) {
        return id;
    }
    freeBytes -= runLength(id);
    return id;
}

1佃声、normCapacity是處理過(guò)的值艺智,如申請(qǐng)大小為1000的內(nèi)存,實(shí)際申請(qǐng)的內(nèi)存大小為1024圾亏。
2十拣、d = maxOrder - (log2(normCapacity) - pageShifts) 可以確定需要在二叉樹的d層開始節(jié)點(diǎn)匹配封拧。
其中pageShifts默認(rèn)值為13,為何是13夭问?因?yàn)橹挥挟?dāng)申請(qǐng)內(nèi)存大小大于2^13(8192)時(shí)才會(huì)使用方法allocateRun分配內(nèi)存泽西。
3、方法allocateNode實(shí)現(xiàn)在二叉樹中進(jìn)行節(jié)點(diǎn)匹配缰趋,具體實(shí)現(xiàn)如下:

private int allocateNode(int d) {
    int id = 1;
    int initial = - (1 << d); 
    //value(id)=memoryMap[id] 
    byte val = value(id); 
    if (val > d) { // unusable
        return -1;
    }
    while (val < d || (id & initial) == 0) { // id & initial == 1 << d for all ids at depth d, for < d it is 0
        id <<= 1;
        val = value(id);
        if (val > d) {
            id ^= 1;
            val = value(id);
        }
    }
    byte value = value(id);
    assert value == d && (id & initial) == 1 << d : String.format("val = %d, id & initial = %d, d = %d",
            value, id & initial, d);
    setValue(id, unusable); // mark as unusable
    updateParentsAlloc(id);
    return id;
}

1捧杉、從根節(jié)點(diǎn)開始遍歷,如果當(dāng)前節(jié)點(diǎn)的val<d秘血,則通過(guò)id <<=1匹配下一層味抖;
2、如果val > d灰粮,則表示存在子節(jié)點(diǎn)被分配的情況仔涩,而且剩余節(jié)點(diǎn)的內(nèi)存大小不夠,此時(shí)需要在兄弟節(jié)點(diǎn)上繼續(xù)查找粘舟;
3熔脂、分配成功的節(jié)點(diǎn)需要標(biāo)記為不可用,防止被再次分配柑肴,在memoryMap對(duì)應(yīng)位置更新為12霞揉;
4、分配節(jié)點(diǎn)完成后嘉抒,其父節(jié)點(diǎn)的狀態(tài)也需要更新零聚,并可能引起更上一層父節(jié)點(diǎn)的更新,實(shí)現(xiàn)如下:

private void updateParentsAlloc(int id) {
    while (id > 1) {
        int parentId = id >>> 1;
        byte val1 = value(id);
        byte val2 = value(id ^ 1);
        byte val = val1 < val2 ? val1 : val2;
        setValue(parentId, val);
        id = parentId;
    }
}

比如節(jié)點(diǎn)2048被分配出去些侍,更新過(guò)程如下:

memoryMap節(jié)點(diǎn)更新

到目前為止隶症,基于poolChunk的節(jié)點(diǎn)分配已經(jīng)完成。

END岗宣。
我是占小狼蚂会。
在魔都艱苦奮斗,白天是上班族耗式,晚上是知識(shí)服務(wù)工作者胁住。
如果讀完覺(jué)得有收獲的話,記得關(guān)注和點(diǎn)贊哦刊咳。
非要打賞的話彪见,我也是不會(huì)拒絕的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末娱挨,一起剝皮案震驚了整個(gè)濱河市余指,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌跷坝,老刑警劉巖酵镜,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碉碉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡淮韭,警方通過(guò)查閱死者的電腦和手機(jī)垢粮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)靠粪,“玉大人蜡吧,你說(shuō)我怎么就攤上這事”优洌” “怎么了斩跌?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)捞慌。 經(jīng)常有香客問(wèn)我耀鸦,道長(zhǎng),這世上最難降的妖魔是什么啸澡? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任袖订,我火速辦了婚禮,結(jié)果婚禮上嗅虏,老公的妹妹穿的比我還像新娘洛姑。我一直安慰自己,他們只是感情好皮服,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布楞艾。 她就那樣靜靜地躺著,像睡著了一般龄广。 火紅的嫁衣襯著肌膚如雪硫眯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天择同,我揣著相機(jī)與錄音两入,去河邊找鬼。 笑死敲才,一個(gè)胖子當(dāng)著我的面吹牛裹纳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播紧武,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼剃氧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了阻星?” 一聲冷哼從身側(cè)響起她我,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后番舆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡矾踱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年恨狈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呛讲。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡禾怠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贝搁,到底是詐尸還是另有隱情吗氏,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布雷逆,位于F島的核電站弦讽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏膀哲。R本人自食惡果不足惜往产,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望某宪。 院中可真熱鬧仿村,春花似錦、人聲如沸兴喂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)衣迷。三九已至畏鼓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蘑险,已是汗流浹背滴肿。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留佃迄,地道東北人泼差。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像呵俏,于是被迫代替她去往敵國(guó)和親堆缘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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