PoolChunk

分配算法描述

總述

image
image

概念

Netty中,PoolChunk實際上就代表了一次申請的內(nèi)存空間夕冲,借助池的設(shè)計她肯,達到多次重復(fù)使用的目的移稳。一個PoolChunk默認會申請16M大小的內(nèi)存空間耙册。

page代表chunk中能被分配的最小單元, 那么chunk看起來就像是一個page的集合.

  • chunkSize = 2^{maxOrder} * pageSize
    • 這里默認chunksize=16M, maxOrder代表最高層數(shù)11, 那么pagesize=16M/2^11=8192=8K

memoryMap

image
* The tree looks like this (the size of each node being mentioned in the parenthesis)
*
* depth=0        1 node (chunkSize)
* depth=1        2 nodes (chunkSize/2)
* ..
* ..
* depth=d        2^d nodes (chunkSize/2^d)
* ..
* depth=maxOrder 2^maxOrder nodes (chunkSize/2^{maxOrder} = pageSize)
*
* depth=maxOrder is the last level and the leafs consist of pages

可以看到Netty通過一個memoryMap將2^11(2048)個page用滿二叉樹的形式存放, 比如當(dāng)我們需要申請4M的空間時, 我們直接去(chunkSize/2^2)也就是depth=2從左開始查找第一個還沒有被使用的節(jié)點.

// memoryMap為2048長的byte數(shù)組
memoryMap = new byte[maxSubpageAllocs << 1];
// depthMap為memoryMap的輔助類
depthMap = new byte[memoryMap.length];
// memoryMap下標(biāo)從1開始
int memoryMapIndex = 1;

// 遍歷每層
for (int d = 0; d <= maxOrder; ++ d) { // move down the tree one level at a time
    // 拿到每層的節(jié)點數(shù)
    int depth = 1 << d;
    // 遍歷每個節(jié)點
    for (int p = 0; p < depth; ++ p) {
        // in each level traverse left to right and set value to the depth of subtree
        // 在memoryMap和depthMap中保存每個節(jié)點的值為當(dāng)前層數(shù)d.
        // depthMap中保存的不可更改, memoryMap中隨著內(nèi)存的分配會變更節(jié)點值
        memoryMap[memoryMapIndex] = (byte) d;
        depthMap[memoryMapIndex] = (byte) d;
        memoryMapIndex ++;
    }
}

算法

memoryMap上每個節(jié)點的值都默認等于它對應(yīng)的depth, chunkSize/2^d的節(jié)點的值都是d.

  • memoryMap[id] = depth_of_id => it is free / unallocated
    • 如果相等, 說明這個節(jié)點沒有被占用, 可以用來分配
  • memoryMap[id] > depth_of_id => at least one of its child nodes is allocated, so we cannot allocate it, but some of its children can still be allocated based on their availability
    • 如果大于depth, 至少說明它有子節(jié)點被占用或分配, 因為我們不能全部滿足, 但是可以部分滿足. 比如一個4M的節(jié)點之前被分配了2M, 那么還可以對外提供2M的空間申請.
  • memoryMap[id] = maxOrder + 1 => the node is fully allocated & thus none of its children can be allocated, it is thus marked as unusable
    • 當(dāng)節(jié)點的值等于maxOrder+1, 也就是12, 說明該節(jié)點的空間被全部分配, 沒有多余的內(nèi)存, 那么將被標(biāo)記為unusable

PoolChunk

PoolChunk(PoolArena<T> arena, T memory, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
    unpooled = false;
    this.arena = arena;
    this.memory = memory;
    this.pageSize = pageSize;
    // 默認為13, 1<<13=8K
    this.pageShifts = pageShifts;
    // memoryMap的最高層數(shù) 11
    this.maxOrder = maxOrder;
    // 16M
    this.chunkSize = chunkSize;
    // 設(shè)定12為unusable
    unusable = (byte) (maxOrder + 1);
    // log2(16M)=24
    log2ChunkSize = log2(chunkSize);
    // 11111111111111111110000000000000
    subpageOverflowMask = ~(pageSize - 1);
    // 初始有16M等待分配
    freeBytes = chunkSize;

    assert maxOrder < 30 : "maxOrder should be < 30, but is: " + maxOrder;
    // 能分配的最大的page大小為 1<<11 2048
    maxSubpageAllocs = 1 << maxOrder;

    // 構(gòu)造memoryMap+depthMap
    ...
    
    // 創(chuàng)建2048個PoolSubpage與page相對應(yīng)
    subpages = newSubpageArray(maxSubpageAllocs);
}

分配

allocate

long allocate(int normCapacity) {
    // 仔細觀察subpageOverflowMask的值就發(fā)現(xiàn)不等于,必然申請的容量要大于或等于pageSize
    if ((normCapacity & subpageOverflowMask) != 0) { // >= pageSize
        return allocateRun(normCapacity);
    } else {
        // 否則必然小于pagesize, 申請subpage
        return allocateSubpage(normCapacity);
    }
}

allocateRun

private long allocateRun(int normCapacity) {
    // 首先normCapacity必須是經(jīng)過PoolArena#normalizeCapacity方法進行標(biāo)準(zhǔn)化的
    // 它要滿足normCapacity>=pagesiz(8k)且為2的冪
    // 只有滿足以上條件,才有可能將層數(shù)限制在maxOrder范圍內(nèi)
    // 公式就是求給定的容量應(yīng)該在哪一層分配
    int d = maxOrder - (log2(normCapacity) - pageShifts);
    // 根據(jù)層數(shù)去請求可分配的節(jié)點
    int id = allocateNode(d);
    if (id < 0) {
        return id;
    }
    // 計算出id節(jié)點占用的大小,算出還剩下多少空間
    freeBytes -= runLength(id);
    return id;
}

runLength

private int runLength(int id) {
    // 很好理解, 2^24=16M, 而x層每一個節(jié)點的大小為2^(24-depth)
    // 16M=2^(24-0),那么x層的節(jié)點大小為2^(24-depth(id)),比如depth=2,算出來2^22=4M
    return 1 << log2ChunkSize - depth(id);
}

allocateNode

img
// 該方法用來查找指定depth的可用節(jié)點
private int allocateNode(int d) {
    // 需要注意的是memoryMap的root從1開始
    int id = 1;
    int initial = - (1 << d); // has last d bits = 0 and rest all = 1
    // 拿到節(jié)點值
    byte val = value(id);
    if (val > d) { // unusable
        return -1;
    }
    
    // 從根開始查找二叉樹
    // 滿足id & initial == 1 << d的才是d層的節(jié)點
    //      假定d=4,id=17, 17&(-16)=16=1<<4
    // 滿足id & initial == 0 則表示所有d層之上的節(jié)點
    // 1. 節(jié)點值小于d, 說明有充足的空間可以分配, 當(dāng)然要繼續(xù)下鉆到具體的d層節(jié)點
    // 2. 如果大于等于d, 說明空間已經(jīng)被占用, 如果還沒有到d層, 說明, 可能剩下空間可以分配
    while (val < d || (id & initial) == 0) { 
        // 找左節(jié)點
        id <<= 1;
        val = value(id);
        // 如果左子樹被占用,找右節(jié)點,如此往復(fù)
        if (val > d) {
            id ^= 1;
            val = value(id);
        }
    }
    byte value = value(id);
    // 滿足節(jié)點未被占用且位于d層
    assert value == d && (id & initial) == 1 << d : String.format("val = %d, id & initial = %d, d = %d", value, id & initial, d);
    // 標(biāo)記該節(jié)點為unusable
    setValue(id, unusable); // mark as unusable
    updateParentsAlloc(id);
    return id;
}
img

updateParentsAlloc

// 從上面的圖示看得更清楚
private void updateParentsAlloc(int id) {
    // 一直從id開始更新到root
    while (id > 1) {
        // 拿到父節(jié)點
        int parentId = id >>> 1;
        byte val1 = value(id);
        // 拿到兄弟節(jié)點
        byte val2 = value(id ^ 1);
        // 取他們兩個較小的節(jié)點值,覆蓋父節(jié)點
        byte val = val1 < val2 ? val1 : val2;
        setValue(parentId, val);
        id = parentId;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末胸私,一起剝皮案震驚了整個濱河市酿傍,隨后出現(xiàn)的幾起案子弛秋,更是在濱河造成了極大的恐慌,老刑警劉巖铺韧,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件多矮,死亡現(xiàn)場離奇詭異,居然都是意外死亡哈打,警方通過查閱死者的電腦和手機塔逃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門讯壶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人患雏,你說我怎么就攤上這事鹏溯“瘴” “怎么了淹仑?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長肺孵。 經(jīng)常有香客問我匀借,道長,這世上最難降的妖魔是什么平窘? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任吓肋,我火速辦了婚禮,結(jié)果婚禮上瑰艘,老公的妹妹穿的比我還像新娘是鬼。我一直安慰自己,他們只是感情好紫新,可當(dāng)我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布均蜜。 她就那樣靜靜地躺著,像睡著了一般芒率。 火紅的嫁衣襯著肌膚如雪囤耳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天偶芍,我揣著相機與錄音充择,去河邊找鬼。 笑死匪蟀,一個胖子當(dāng)著我的面吹牛椎麦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播材彪,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼铃剔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了查刻?” 一聲冷哼從身側(cè)響起键兜,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎穗泵,沒想到半個月后普气,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡佃延,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年现诀,在試婚紗的時候發(fā)現(xiàn)自己被綠了夷磕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡仔沿,死狀恐怖坐桩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情封锉,我是刑警寧澤绵跷,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站成福,受9級特大地震影響碾局,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜奴艾,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一净当、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蕴潦,春花似錦像啼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至萄传,卻和暖如春甚颂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背秀菱。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工振诬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衍菱。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓赶么,卻偏偏與公主長得像,于是被迫代替她去往敵國和親脊串。 傳聞我的和親對象是個殘疾皇子辫呻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,960評論 2 355

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