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

上一節(jié)中分析了如何在poolChunk中分配一塊大于pageSize的內(nèi)存,但在實際應(yīng)用中,存在很多分配小內(nèi)存的情況,如果也占用一個page,明顯很浪費。針對這種情況,Netty提供了PoolSubpage把poolChunk的一個page節(jié)點8k內(nèi)存劃分成更小的內(nèi)存段,通過對每個內(nèi)存段的標(biāo)記與清理標(biāo)記進行內(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;
    //通過對每一個二進制位的標(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;
    // 下一個可用的位置
    private int nextAvail;
    // 可用的段數(shù)量
    private int numAvail;       
    ...
}

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

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

因為4096<pageSize(8192),所以采用allocateSubpage進行內(nèi)存分配,具體實現(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負責(zé)管理PoolChunk和PoolSubpage;
2、allocateNode負責(zé)在二叉樹中找到匹配的節(jié)點,和poolChunk不同的是,只匹配葉子節(jié)點;
3、poolChunk中維護了一個大小為2048的poolSubpage數(shù)組,分別對應(yīng)二叉樹中2048個葉子節(jié)點,假設(shè)本次分配到節(jié)點2048,則取出poolSubpage數(shù)組第一個元素subpage;
4、如果subpage為空,則進行初始化,并加入到poolSubpage數(shù)組;

subpage初始化實現(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、默認初始化bitmap長度為8,這里解釋一下為什么只需要8個元素:其中分配內(nèi)存大小都是處理過的,最小為16,說明一個page可以分成8192/16 = 512個內(nèi)存段,一個long有64位,可以描述64個內(nèi)存段,這樣只需要512/64 = 8個long就可以描述全部內(nèi)存段了。
2、init根據(jù)當(dāng)前需要分配的內(nèi)存大小,確定需要多少個bitmap元素,實現(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);
}

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

  1. 比如,當(dāng)前申請大小4096的內(nèi)存,maxNumElems 和 numAvail 為2,說明一個page被拆分成2個內(nèi)存段,2 >>> 6 = 0,且2 & 63 != 0,所以bitmapLength為1,說明只需要一個long就可以描述2個內(nèi)存段狀態(tài)。
  2. 如果當(dāng)前申請大小32的內(nèi)存,maxNumElems 和 numAvail 為 256,說明一個page被拆分成256個內(nèi)存段, 256>>> 6 = 4,說明需要4個long描述256個內(nèi)存段狀態(tài)。

下面看看subpage是如何進行內(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負責(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的那一部分二進制數(shù)抹掉,得到一個小于64的數(shù)r;
4、bitmap[q] |= 1L << r將對應(yīng)位置q設(shè)置為1;

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

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

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

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

1、如果nextAvail大于等于0,說明nextAvail指向了下一個可分配的內(nèi)存段,直接返回nextAvail值;
2、每次分配完成,nextAvail被置為-1,這時只能通過方法findNextAvail重新計算出下一個可分配的內(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說明這個long所描述的64個內(nèi)存段還有未分配的;
2、(bits & 1) == 0 用來判斷該位置是否未分配,否則bits又移一位,從左到右遍歷值為0的位置;

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

END。
我是占小狼。
在魔都艱苦奮斗,白天是上班族,晚上是知識服務(wù)工作者。
如果讀完覺得有收獲的話,記得關(guān)注和點贊哦。
非要打賞的話,我也是不會拒絕的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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