溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Netty分布式ByteBuf如何使用page級(jí)別的內(nèi)存分配

發(fā)布時(shí)間:2022-03-29 09:10:32 來源:億速云 閱讀:138 作者:小新 欄目:開發(fā)技術(shù)

這篇文章主要為大家展示了“Netty分布式ByteBuf如何使用page級(jí)別的內(nèi)存分配”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“Netty分布式ByteBuf如何使用page級(jí)別的內(nèi)存分配”這篇文章吧。

netty內(nèi)存分配數(shù)據(jù)結(jié)構(gòu)

之前我們介紹過, netty內(nèi)存分配的單位是chunk, 一個(gè)chunk的大小是16MB, 實(shí)際上每個(gè)chunk, 都以雙向鏈表的形式保存在一個(gè)chunkList中, 而多個(gè)chunkList, 同樣也是雙向鏈表進(jìn)行關(guān)聯(lián)的, 大概結(jié)構(gòu)如下所示:

Netty分布式ByteBuf如何使用page級(jí)別的內(nèi)存分配

在chunkList中, 是根據(jù)chunk的內(nèi)存使用率歸到一個(gè)chunkList中, 這樣, 在內(nèi)存分配時(shí), 會(huì)根據(jù)百分比找到相應(yīng)的chunkList, 在chunkList中選擇一個(gè)chunk進(jìn)行內(nèi)存分配 

我們看PoolArena中有關(guān)chunkList的成員變量

private final PoolChunkList<T> q050;
private final PoolChunkList<T> q025;
private final PoolChunkList<T> q000;
private final PoolChunkList<T> qInit;
private final PoolChunkList<T> q075;
private final PoolChunkList<T> q100;

這里總共定義了6個(gè)chunkList, 并在構(gòu)造方法將其進(jìn)行初始化

跟到其構(gòu)造方法中:

protected PoolArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
    //代碼省略
    q100 = new PoolChunkList<T>(null, 100, Integer.MAX_VALUE, chunkSize);
    q075 = new PoolChunkList<T>(q100, 75, 100, chunkSize); 
    q050 = new PoolChunkList<T>(q075, 50, 100, chunkSize);
    q025 = new PoolChunkList<T>(q050, 25, 75, chunkSize);
    q000 = new PoolChunkList<T>(q025, 1, 50, chunkSize);
    qInit = new PoolChunkList<T>(q000, Integer.MIN_VALUE, 25, chunkSize);
    //用雙向鏈表的方式進(jìn)行連接
    q100.prevList(q075);
    q075.prevList(q050);
    q050.prevList(q025);
    q025.prevList(q000);
    q000.prevList(null);
    qInit.prevList(qInit);
    //代碼省略
}

首先通過new PoolChunkList()這種方式將每個(gè)chunkList進(jìn)行創(chuàng)建, 我們以 q050 = new PoolChunkList<T>(q075, 50, 100, chunkSize) 為例進(jìn)行簡(jiǎn)單的介紹

q075表示當(dāng)前q50的下一個(gè)節(jié)點(diǎn)是q075, 剛才我們講過ChunkList是通過雙向鏈表進(jìn)行關(guān)聯(lián)的, 所以這里不難理解

參數(shù)50和100表示當(dāng)前chunkList中存儲(chǔ)的chunk的內(nèi)存使用率都在50%到100%之間, 最后chunkSize為其設(shè)置大小

創(chuàng)建完ChunkList之后, 再設(shè)置其上一個(gè)節(jié)點(diǎn), q050.prevList(q025)為例, 這里代表當(dāng)前chunkList的上一個(gè)節(jié)點(diǎn)是q025

以這種方式創(chuàng)建完成之后, chunkList的節(jié)點(diǎn)關(guān)系變成了如下圖所示:

Netty分布式ByteBuf如何使用page級(jí)別的內(nèi)存分配

netty中, chunk又包含了多個(gè)page, 每個(gè)page的大小為8k, 如果要分配16k的內(nèi)存, 則在在chunk中找到連續(xù)的兩個(gè)page就可以分配, 對(duì)應(yīng)關(guān)系如下:

Netty分布式ByteBuf如何使用page級(jí)別的內(nèi)存分配

很多場(chǎng)景下, 為緩沖區(qū)分配8k的內(nèi)存也是一種浪費(fèi), 比如只需要分配2k的緩沖區(qū), 如果使用8k會(huì)造成6k的浪費(fèi), 這種情況, netty又會(huì)將page切分成多個(gè)subpage, 每個(gè)subpage大小要根據(jù)分配的緩沖區(qū)大小而指定, 比如要分配2k的內(nèi)存, 就會(huì)將一個(gè)page切分成4個(gè)subpage, 每個(gè)subpage的大小為2k, 如圖:

Netty分布式ByteBuf如何使用page級(jí)別的內(nèi)存分配

我們看PoolSubpage的屬性

final PoolChunk<T> chunk;
private final int memoryMapIdx;
private final int runOffset;
private final int pageSize; 
private final long[] bitmap;
PoolSubpage<T> prev;
PoolSubpage<T> next;
boolean doNotDestroy; 
int elemSize;

chunk代表其子頁屬于哪個(gè)chunk

bitmap用于記錄子頁的內(nèi)存分配情況

prev和next, 代表子頁是按照雙向鏈表進(jìn)行關(guān)聯(lián)的, 這里分別指向上一個(gè)和下一個(gè)節(jié)點(diǎn)

elemSize屬性, 代表的就是這個(gè)子頁是按照多大內(nèi)存進(jìn)行劃分的, 如果按照1k劃分, 則可以劃分出8個(gè)子頁

簡(jiǎn)單介紹了內(nèi)存分配的數(shù)據(jù)結(jié)構(gòu), 我們開始剖析netty在page級(jí)別上分配內(nèi)存的流程:

我們回到PoolArena的allocate方法

private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
    //規(guī)格化
    final int normCapacity = normalizeCapacity(reqCapacity);
    if (isTinyOrSmall(normCapacity)) { 
        int tableIdx;
        PoolSubpage<T>[] table;
        //判斷是不是tinty
        boolean tiny = isTiny(normCapacity);
        if (tiny) { // < 512
            //緩存分配
            if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
                return;
            }
            //通過tinyIdx拿到tableIdx
            tableIdx = tinyIdx(normCapacity);
            //subpage的數(shù)組
            table = tinySubpagePools;
        } else {
            if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
                return;
            }
            tableIdx = smallIdx(normCapacity);
            table = smallSubpagePools;
        }

        //拿到對(duì)應(yīng)的節(jié)點(diǎn)
        final PoolSubpage<T> head = table[tableIdx];

        synchronized (head) {
            final PoolSubpage<T> s = head.next;
            //默認(rèn)情況下, head的next也是自身
            if (s != head) {
                assert s.doNotDestroy && s.elemSize == normCapacity;
                long handle = s.allocate();
                assert handle >= 0;
                s.chunk.initBufWithSubpage(buf, handle, reqCapacity);

                if (tiny) {
                    allocationsTiny.increment();
                } else {
                    allocationsSmall.increment();
                }
                return;
            }
        }
        allocateNormal(buf, reqCapacity, normCapacity);
        return;
    }
    if (normCapacity <= chunkSize) {
        //首先在緩存上進(jìn)行內(nèi)存分配
        if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
            //分配成功, 返回
            return;
        }
        //分配不成功, 做實(shí)際的內(nèi)存分配
        allocateNormal(buf, reqCapacity, normCapacity);
    } else {
        //大于這個(gè)值, 就不在緩存上分配
        allocateHuge(buf, reqCapacity);
    }
}

我們之前講過, 如果在緩存中分配不成功, 則會(huì)開辟一塊連續(xù)的內(nèi)存進(jìn)行緩沖區(qū)分配, 這里我們先跳過isTinyOrSmall(normCapacity)往后的代碼, 下一小節(jié)進(jìn)行分析

首先 if (normCapacity <= chunkSize) 說明其小于16MB, 然后首先在緩存中分配, 因?yàn)樽畛蹙彺嬷袥]有值, 所以會(huì)走到allocateNormal(buf, reqCapacity, normCapacity), 這里實(shí)際上就是在page級(jí)別上進(jìn)行分配, 分配一個(gè)或者多個(gè)page的空間

我們跟進(jìn)allocateNormal

private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
    //首先在原來的chunk上進(jìn)行內(nèi)存分配(1)
    if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
        q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
        q075.allocate(buf, reqCapacity, normCapacity)) {
        ++allocationsNormal;
        return;
    }
    //創(chuàng)建chunk進(jìn)行內(nèi)存分配(2)
    PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
    long handle = c.allocate(normCapacity);
    ++allocationsNormal;
    assert handle > 0;
    //初始化byteBuf(3)
    c.initBuf(buf, handle, reqCapacity); 
    qInit.add(c);
}

這里主要拆解了如下步驟

1. 在原有的chunk中進(jìn)行分配

2. 創(chuàng)建chunk進(jìn)行分配

3. 初始化ByteBuf

首先我們看第一步, 在原有的chunk中進(jìn)行分配:

if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
    q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
    q075.allocate(buf, reqCapacity, normCapacity)) {
    ++allocationsNormal;
    return;
}

我們之前講過, chunkList是存儲(chǔ)不同內(nèi)存使用量的chunk集合, 每個(gè)chunkList通過雙向鏈表的形式進(jìn)行關(guān)聯(lián), 這里的q050.allocate(buf, reqCapacity, normCapacity)就代表首先在q050這個(gè)chunkList上進(jìn)行內(nèi)存分配

我們以q050為例進(jìn)行分析, 跟到q050.allocate(buf, reqCapacity, normCapacity)方法中:

boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
    if (head == null || normCapacity > maxCapacity) {
        return false;
    }
    //從head節(jié)點(diǎn)往下遍歷
    for (PoolChunk<T> cur = head;;) {
        long handle = cur.allocate(normCapacity);
        if (handle < 0) {
            cur = cur.next;
            if (cur == null) {
                return false;
            }
        } else {
            cur.initBuf(buf, handle, reqCapacity);
            if (cur.usage() >= maxUsage) {
                remove(cur);
                nextList.add(cur);
            }
            return true;
        }
    }
}

首先會(huì)從head節(jié)點(diǎn)往下遍歷

 long handle = cur.allocate(normCapacity) 

表示對(duì)于每個(gè)chunk, 都嘗試去分配

 if (handle < 0) 說明沒有分配到, 則通過cur = cur.next找到下一個(gè)節(jié)點(diǎn)繼續(xù)進(jìn)行分配, 我們講過chunk也是通過雙向鏈表進(jìn)行關(guān)聯(lián)的, 所以對(duì)這塊邏輯應(yīng)該不會(huì)陌生

如果handle大于0說明已經(jīng)分配到了內(nèi)存, 則通過cur.initBuf(buf, handle, reqCapacity)對(duì)byteBuf進(jìn)行初始化

 if (cur.usage() >= maxUsage) 代表當(dāng)前chunk的內(nèi)存使用率大于其最大使用率, 則通過remove(cur)從當(dāng)前的chunkList中移除, 再通過nextList.add(cur)添加到下一個(gè)chunkList中

我們?cè)倩氐絇oolArena的allocateNormal方法中:

我們看第二步

PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize)

這里的參數(shù)pageSize是8192, 也就是8k

maxOrder為11

pageShifts為13,   2的13次方正好是8192, 也就是8k

chunkSize為16777216, 也就是16MB

這里的參數(shù)值可以通過debug的方式跟蹤到

因?yàn)槲覀兊氖纠嵌淹鈨?nèi)存, newChunk(pageSize, maxOrder, pageShifts, chunkSize)所以會(huì)走到DirectArena的newChunk方法中:

protected PoolChunk<ByteBuffer> newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize) {
    return new PoolChunk<ByteBuffer>( 
            this, allocateDirect(chunkSize), 
            pageSize, maxOrder, pageShifts, chunkSize);
}

這里直接通過構(gòu)造函數(shù)創(chuàng)建了一個(gè)chunk

allocateDirect(chunkSize)這里是通過jdk的api的申請(qǐng)了一塊直接內(nèi)存, 我們跟到PoolChunk的構(gòu)造函數(shù)中:

PoolChunk(PoolArena<T> arena, T memory, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
    unpooled = false;
    this.arena = arena;
    //memeory為一個(gè)ByteBuf
    this.memory = memory;
    //8k
    this.pageSize = pageSize;
    //13
    this.pageShifts = pageShifts;
    //11
    this.maxOrder = maxOrder;
    this.chunkSize = chunkSize;
    unusable = (byte) (maxOrder + 1);
    log2ChunkSize = log2(chunkSize);
    subpageOverflowMask = ~(pageSize - 1);
    freeBytes = chunkSize;
    assert maxOrder < 30 : "maxOrder should be < 30, but is: " + maxOrder;
    maxSubpageAllocs = 1 << maxOrder;
    //節(jié)點(diǎn)數(shù)量為4096
    memoryMap = new byte[maxSubpageAllocs << 1];
    //也是4096個(gè)節(jié)點(diǎn)
    depthMap = new byte[memoryMap.length];
    int memoryMapIndex = 1;
    //d相當(dāng)于一個(gè)深度, 賦值的內(nèi)容代表當(dāng)前節(jié)點(diǎn)的深度
    for (int d = 0; d <= maxOrder; ++ d) {
        int depth = 1 << d;
        for (int p = 0; p < depth; ++ p) {
            memoryMap[memoryMapIndex] = (byte) d;
            depthMap[memoryMapIndex] = (byte) d;
            memoryMapIndex ++;
        }
    }
    subpages = newSubpageArray(maxSubpageAllocs);
}

首先將參數(shù)傳入的值進(jìn)行賦值

 this.memory = memory 就是將參數(shù)中創(chuàng)建的堆外內(nèi)存進(jìn)行保存, 就是chunk所指向的那塊連續(xù)的內(nèi)存, 在這個(gè)chunk中所分配的ByteBuf, 都會(huì)在這塊內(nèi)存中進(jìn)行讀寫

我們重點(diǎn)關(guān)注 memoryMap = new byte[maxSubpageAllocs << 1] 

和 depthMap = new byte[memoryMap.length] 這兩步

首先看 memoryMap = new byte[maxSubpageAllocs << 1] 

這里初始化了一個(gè)字節(jié)數(shù)組memoryMap, 大小為maxSubpageAllocs << 1, 也就是4096

 depthMap = new byte[memoryMap.length] 同樣也是初始化了一個(gè)字節(jié)數(shù)組, 大小為memoryMap的大小, 也就是4096

繼續(xù)往下分析之前, 我們看chunk的一個(gè)層級(jí)關(guān)系

Netty分布式ByteBuf如何使用page級(jí)別的內(nèi)存分配

這是一個(gè)二叉樹的結(jié)構(gòu), 左側(cè)的數(shù)字代表層級(jí), 右側(cè)代表一塊連續(xù)的內(nèi)存, 每個(gè)父節(jié)點(diǎn)下又拆分成多個(gè)子節(jié)點(diǎn), 最頂層表示的內(nèi)存范圍為0-16MB, 其又下分為兩層, 范圍為0-8MB, 8-16MB, 以此類推, 最后到11層, 以8k的大小劃分, 也就是一個(gè)page的大小

如果我們分配一個(gè)8mb的緩沖區(qū), 則會(huì)將第二層的第一個(gè)節(jié)點(diǎn), 也就是0-8這個(gè)連續(xù)的內(nèi)存進(jìn)行分配, 分配完成之后, 會(huì)將這個(gè)節(jié)點(diǎn)設(shè)置為不可用, 具體邏輯后面會(huì)講解

結(jié)合上面的圖, 我們?cè)倏礃?gòu)造方法中的for循環(huán):

for (int d = 0; d <= maxOrder; ++ d) {
    int depth = 1 << d;
    for (int p = 0; p < depth; ++ p) {
        memoryMap[memoryMapIndex] = (byte) d;
        depthMap[memoryMapIndex] = (byte) d;
        memoryMapIndex ++;
    }
}

實(shí)際上這個(gè)for循環(huán)就是將上面的結(jié)構(gòu)包裝成一個(gè)字節(jié)數(shù)組memoryMap, 外層循環(huán)用于控制層數(shù), 內(nèi)層循環(huán)用于控制里面每層的節(jié)點(diǎn), 這里經(jīng)過循環(huán)之后, memoryMap和depthMap內(nèi)容為以下表現(xiàn)形式:

[0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4...........]

這里注意一下, 因?yàn)槌绦蛑袛?shù)組的下標(biāo)是從1開始設(shè)置的, 所以第零個(gè)節(jié)點(diǎn)元素為默認(rèn)值0

這里數(shù)字代表層級(jí), 同時(shí)也代表了當(dāng)前層級(jí)的節(jié)點(diǎn), 相同的數(shù)字個(gè)數(shù)就是這一層級(jí)的節(jié)點(diǎn)數(shù)

其中0為2個(gè)(因?yàn)檫@里分配時(shí)下標(biāo)是從1開始的, 所以第0個(gè)位置是默認(rèn)值0, 實(shí)際上第零層元素只有一個(gè), 就是頭結(jié)點(diǎn)), 1為2個(gè), 2為4個(gè), 3為8個(gè), 4為16個(gè), n為2的n次方個(gè), 直到11, 也就是11有2的11次方個(gè)

我們?cè)倩氐絇oolArena的allocateNormal方法中

private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
    //首先在原來的chunk上進(jìn)行內(nèi)存分配(1)
    if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
        q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
        q075.allocate(buf, reqCapacity, normCapacity)) {
        ++allocationsNormal;
        return;
    }
    //創(chuàng)建chunk進(jìn)行內(nèi)存分配(2)
    PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
    long handle = c.allocate(normCapacity);
    ++allocationsNormal;
    assert handle > 0;
    //初始化byteBuf(3)
    c.initBuf(buf, handle, reqCapacity); 
    qInit.add(c);
}

我們繼續(xù)剖析 long handle = c.allocate(normCapacity) 這步

跟到allocate(normCapacity)中

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

如果分配是以page為單位, 則走到allocateRun(normCapacity)方法中, 跟進(jì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;
}

 int d = maxOrder - (log2(normCapacity) - pageShifts) 表示根據(jù)normCapacity計(jì)算出圖5-8-5中的第幾層

 int id = allocateNode(d) 表示根據(jù)層級(jí)關(guān)系, 去分配一個(gè)節(jié)點(diǎn), 其中id代表memoryMap中的下標(biāo)

我們跟到allocateNode方法中

private int allocateNode(int d) {
    //下標(biāo)初始值為1
    int id = 1;
    //代表當(dāng)前層級(jí)第一個(gè)節(jié)點(diǎn)的初始下標(biāo)
    int initial = - (1 << d);
    //獲取第一個(gè)節(jié)點(diǎn)的值
    byte val = value(id);
    //如果值大于層級(jí), 說明chunk不可用
    if (val > d) { 
        return -1;
    }
    //當(dāng)前下標(biāo)對(duì)應(yīng)的節(jié)點(diǎn)值如果小于層級(jí), 或者當(dāng)前下標(biāo)小于層級(jí)的初始下標(biāo)
    while (val < d || (id & initial) == 0) {
        //當(dāng)前下標(biāo)乘以2, 代表下當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)的起始位置
        id <<= 1;
        //獲得id位置的值
        val = value(id);
        //如果當(dāng)前節(jié)點(diǎn)值大于層數(shù)(節(jié)點(diǎn)不可用)
        if (val > d) {
            //id為偶數(shù)則+1, id為奇數(shù)則-1(拿的是其兄弟節(jié)點(diǎn))
            id ^= 1;
            //獲取id的值
            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);
    //將找到的節(jié)點(diǎn)設(shè)置為不可用
    setValue(id, unusable); 
    //逐層往上標(biāo)記被使用
    updateParentsAlloc(id);
    return id;
}

這里是實(shí)際上是從第一個(gè)節(jié)點(diǎn)往下找, 找到層級(jí)為d未被使用的節(jié)點(diǎn), 我們可以通過注釋體會(huì)其邏輯

找到相關(guān)節(jié)點(diǎn)后通過setValue將當(dāng)前節(jié)點(diǎn)設(shè)置為不可用, 其中id是當(dāng)前節(jié)點(diǎn)的下標(biāo), unusable代表一個(gè)不可用的值, 這里是12, 因?yàn)槲覀兊膶蛹?jí)只有12層, 所以設(shè)置為12之后就相當(dāng)于標(biāo)記不可用

設(shè)置成不可用之后, 通過updateParentsAlloc(id)逐層設(shè)置為被使用

我們跟進(jìn)updateParentsAlloc方法

private void updateParentsAlloc(int id) {
    while (id > 1) {
        //取到當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)的id
        int parentId = id >>> 1;
        //獲取當(dāng)前節(jié)點(diǎn)的值
        byte val1 = value(id);
        //找到當(dāng)前節(jié)點(diǎn)的兄弟節(jié)點(diǎn)
        byte val2 = value(id ^ 1);
        //如果當(dāng)前節(jié)點(diǎn)值小于兄弟節(jié)點(diǎn), 則保存當(dāng)前節(jié)點(diǎn)值到val, 否則, 保存兄弟節(jié)點(diǎn)值到val 
        //如果當(dāng)前節(jié)點(diǎn)是不可用, 則當(dāng)前節(jié)點(diǎn)值是12, 大于兄弟節(jié)點(diǎn)的值, 所以這里將兄弟節(jié)點(diǎn)的值進(jìn)行保存
        byte val = val1 < val2 ? val1 : val2;
        //將val的值設(shè)置為父節(jié)點(diǎn)下標(biāo)所對(duì)應(yīng)的值
        setValue(parentId, val);
        //id設(shè)置為父節(jié)點(diǎn)id, 繼續(xù)循環(huán)
        id = parentId;
    }
}

這里其實(shí)是將循環(huán)將兄弟節(jié)點(diǎn)的值替換成父節(jié)點(diǎn)的值, 我們可以通過注釋仔細(xì)的進(jìn)行邏輯分析

如果實(shí)在理解有困難, 我通過畫圖幫助大家理解:

簡(jiǎn)單起見, 我們這里只設(shè)置三層:

Netty分布式ByteBuf如何使用page級(jí)別的內(nèi)存分配

這里我們模擬其分配場(chǎng)景, 假設(shè)只有三層, 其中index代表數(shù)組memoryMap的下標(biāo), value代表其值, memoryMap中的值就為[0, 0, 1, 1, 2, 2, 2, 2]

我們要分配一個(gè)4MB的byteBuf, 在我們調(diào)用allocateNode(int d)中傳入的d是2, 也就是第二層

根據(jù)我們上面分分析的邏輯這里會(huì)找到第二層的第一個(gè)節(jié)點(diǎn), 也就是0-4mb這個(gè)節(jié)點(diǎn), 找到之后將其設(shè)置為不可用, 這樣memoryMap中的值就為[0, 0, 1, 1, 12, 2, 2, 2]

二叉樹的結(jié)構(gòu)就會(huì)變?yōu)?

Netty分布式ByteBuf如何使用page級(jí)別的內(nèi)存分配

注意標(biāo)紅部分, 將index為4的節(jié)點(diǎn)設(shè)置為了不可用

將這個(gè)節(jié)點(diǎn)設(shè)置為不可用之后, 則會(huì)將進(jìn)行向上設(shè)置不可用, 循環(huán)將兄弟節(jié)點(diǎn)數(shù)值較小的節(jié)點(diǎn)替換到父節(jié)點(diǎn), 也就是將index為2的節(jié)點(diǎn)的值替換成了index的為5的節(jié)點(diǎn)的值, 這樣數(shù)組的值就會(huì)變?yōu)閇0, 1, 2, 1, 12, 2, 2, 2]

二叉樹的結(jié)構(gòu)變?yōu)?

Netty分布式ByteBuf如何使用page級(jí)別的內(nèi)存分配

注意, 這里節(jié)點(diǎn)標(biāo)紅僅僅代表節(jié)點(diǎn)變化, 并不是當(dāng)前節(jié)點(diǎn)為不可用狀態(tài), 真正不可用狀態(tài)的判斷依據(jù)是value值為12

這樣, 如果再次分配一個(gè)4MB內(nèi)存的ByteBuf, 根據(jù)其邏輯, 則會(huì)找到第二層的第二個(gè)節(jié)點(diǎn), 也就是4-8MB

再根據(jù)我們的邏輯, 通過向上設(shè)置不可用, index為2就會(huì)設(shè)置成不可用狀態(tài), 將value的值設(shè)置為12, 數(shù)組數(shù)值變?yōu)閇0, 1, 12, 1, 12, 12, 2, 2]二叉樹如下圖所示:

Netty分布式ByteBuf如何使用page級(jí)別的內(nèi)存分配

這樣我們看到, 通過分配兩個(gè)4mb的byteBuf之后, 當(dāng)前節(jié)點(diǎn)和其父節(jié)點(diǎn)都會(huì)設(shè)置成不可用狀態(tài), 當(dāng)index=2的節(jié)點(diǎn)設(shè)置為不可用之后, 將不會(huì)再找這個(gè)節(jié)點(diǎn)下的子節(jié)點(diǎn)

以此類推, 直到所有的內(nèi)存分配完畢的時(shí)候, index為1的節(jié)點(diǎn), 也會(huì)變成不可用狀態(tài), 這樣所有的page就分配完畢, chunk中再無可用節(jié)點(diǎn)

我們?cè)倩氐絇oolArena的allocateNormal方法中

private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
    //首先在原來的chunk上進(jìn)行內(nèi)存分配(1)
    if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
        q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
        q075.allocate(buf, reqCapacity, normCapacity)) {
        ++allocationsNormal;
        return;
    }
    //創(chuàng)建chunk進(jìn)行內(nèi)存分配(2)
    PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
    long handle = c.allocate(normCapacity);
    ++allocationsNormal;
    assert handle > 0;
    //初始化byteBuf(3)
    c.initBuf(buf, handle, reqCapacity);
    qInit.add(c);
}

通過以上邏輯我們知道, long handle = c.allocate(normCapacity)這一步, 其實(shí)返回的就是memoryMap的一個(gè)下標(biāo), 通過這個(gè)下標(biāo), 我們能唯一的定位一塊內(nèi)存

繼續(xù)往下跟, 通過c.initBuf(buf, handle, reqCapacity)初始化ByteBuf之后, 通過qInit.add(c)將新創(chuàng)建的chunk添加到chunkList中

我們跟到initBuf方法中去

void initBuf(PooledByteBuf<T> buf, long handle, int reqCapacity) {
    int memoryMapIdx = memoryMapIdx(handle);
    int bitmapIdx = bitmapIdx(handle);
    if (bitmapIdx == 0) {
        byte val = value(memoryMapIdx);
        assert val == unusable : String.valueOf(val);
        buf.init(this, handle, runOffset(memoryMapIdx), reqCapacity, runLength(memoryMapIdx), 
                 arena.parent.threadCache());
    } else {
        initBufWithSubpage(buf, handle, bitmapIdx, reqCapacity);
    }
}

這里通過memoryMapIdx(handle)找到memoryMap的下標(biāo), 其實(shí)就是handle的值

bitmapIdx(handle)是有關(guān)subPage中使用到的邏輯, 如果是page級(jí)別的分配, 這里只返回0, 所以進(jìn)入到if塊中

if中首先斷言當(dāng)前節(jié)點(diǎn)是不是不可用狀態(tài), 然后通過init方法進(jìn)行初始化

其中runOffset(memoryMapIdx)表示偏移量, 偏移量相當(dāng)于分配給緩沖區(qū)的這塊內(nèi)存相對(duì)于chunk中申請(qǐng)的內(nèi)存的首地址偏移了多少

參數(shù)runLength(memoryMapIdx), 表示根據(jù)下標(biāo)獲取可分配的最大長(zhǎng)度

我們跟到init中, 這里會(huì)走到PooledByteBuf的init方法中:

void init(PoolChunk<T> chunk, long handle, int offset, int length, int maxLength, PoolThreadCache cache) {
    //初始化
    assert handle >= 0;
    assert chunk != null;
    //在哪一塊內(nèi)存上進(jìn)行分配的
    this.chunk = chunk;
    //這一塊內(nèi)存上的哪一塊連續(xù)內(nèi)存
    this.handle = handle;
    memory = chunk.memory;
    this.offset = offset;
    this.length = length;
    this.maxLength = maxLength;
    tmpNioBuf = null;
    this.cache = cache;
}

這里又是我們熟悉的部分, 將屬性進(jìn)行了初始化

以上是“Netty分布式ByteBuf如何使用page級(jí)別的內(nèi)存分配”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI