這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)如何理解Netty內(nèi)存管理 PoolChunk,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
創(chuàng)新新互聯(lián),憑借十余年的成都網(wǎng)站設(shè)計、網(wǎng)站制作經(jīng)驗,本著真心·誠心服務(wù)的企業(yè)理念服務(wù)于成都中小企業(yè)設(shè)計網(wǎng)站有近千家案例。做網(wǎng)站建設(shè),選成都創(chuàng)新互聯(lián)。
多年之前,從C內(nèi)存的手動管理上升到j(luò)ava的自動GC,是歷史的巨大進(jìn)步。然而多年之后,netty的內(nèi)存實現(xiàn)又曲線的回到了手動管理模式,正印證了馬克思哲學(xué)觀:社會總是在螺旋式前進(jìn)的,沒有永遠(yuǎn)的最好。的確,就內(nèi)存管理而言,GC給程序員帶來的價值是不言而喻的,不僅大大的降低了程序員的負(fù)擔(dān),而且也極大的減少了內(nèi)存管理帶來的Crash困擾,不過也有很多情況,可能手動的內(nèi)存管理更為合適。
接下去準(zhǔn)備幾個篇幅對Netty的內(nèi)存管理進(jìn)行深入分析。
PoolChunk
為了能夠簡單的操作內(nèi)存,必須保證每次分配到的內(nèi)存時連續(xù)的。Netty中底層的內(nèi)存分配和回收管理主要由PoolChunk實現(xiàn),其內(nèi)部維護(hù)一棵平衡二叉樹memoryMap,所有子節(jié)點管理的內(nèi)存也屬于其父節(jié)點。
poolChunk默認(rèn)由2048個page組成,一個page默認(rèn)大小為8k,圖中節(jié)點的值為在數(shù)組memoryMap的下標(biāo)。
1、如果需要分配大小8k的內(nèi)存,則只需要在第11層,找到第一個可用節(jié)點即可。
2、如果需要分配大小16k的內(nèi)存,則只需要在第10層,找到第一個可用節(jié)點即可。
3、如果節(jié)點1024存在一個已經(jīng)被分配的子節(jié)點2048,則該節(jié)點不能被分配,如需要分配大小16k的內(nèi)存,這個時候節(jié)點2048已被分配,節(jié)點2049未被分配,就不能直接分配節(jié)點1024,因為該節(jié)點目前只剩下8k內(nèi)存。
poolChunk內(nèi)部會保證每次分配內(nèi)存大小為8K*(2n),為了分配一個大小為chunkSize/(2k)的節(jié)點,需要在深度為k的層從左開始匹配節(jié)點,那么如何快速的分配到指定內(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ù)組中每個位置保存的是該節(jié)點所在的層數(shù),有什么作用?對于節(jié)點512,其層數(shù)是9,則:
1、如果memoryMap[512] = 9,則表示其本身到下面所有的子節(jié)點都可以被分配;
2、如果memoryMap[512] = 10, 則表示節(jié)點512下有子節(jié)點已經(jīng)分配過,則該節(jié)點不能直接被分配,而其子節(jié)點中的第10層還存在未分配的節(jié)點;
3、如果memoryMap[512] = 12 (即總層數(shù) + 1), 可分配的深度已經(jīng)大于總層數(shù), 則表示該節(jié)點下的所有子節(jié)點都已經(jīng)被分配。
下面看看如何向PoolChunk申請一段內(nèi)存:
long allocate(int normCapacity) { if ((normCapacity & subpageOverflowMask) != 0) { // >= pageSize return allocateRun(normCapacity); } else { return allocateSubpage(normCapacity); } }
1、當(dāng)需要分配的內(nèi)存大于pageSize時,使用allocateRun實現(xiàn)內(nèi)存分配。
2、否則使用方法allocateSubpage分配內(nèi)存,在allocateSubpage實現(xiàn)中,會把一個page分割成多段,進(jìn)行內(nèi)存分配。
這里先看看allocateRun是如何實現(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是處理過的值,如申請大小為1000的內(nèi)存,實際申請的內(nèi)存大小為1024。
2、d = maxOrder - (log2(normCapacity) - pageShifts) 可以確定需要在二叉樹的d層開始節(jié)點匹配。
其中pageShifts默認(rèn)值為13,為何是13?因為只有當(dāng)申請內(nèi)存大小大于2^13(8192)時才會使用方法allocateRun分配內(nèi)存。
3、方法allocateNode實現(xiàn)在二叉樹中進(jìn)行節(jié)點匹配,具體實現(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é)點開始遍歷,如果當(dāng)前節(jié)點的val
3、分配成功的節(jié)點需要標(biāo)記為不可用,防止被再次分配,在memoryMap對應(yīng)位置更新為12;
4、分配節(jié)點完成后,其父節(jié)點的狀態(tài)也需要更新,并可能引起更上一層父節(jié)點的更新,實現(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é)點2048被分配出去,更新過程如下:
到目前為止,基于poolChunk的節(jié)點分配已經(jīng)完成。
上述就是小編為大家分享的如何理解Netty內(nèi)存管理 PoolChunk了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。