netty內存池之poolSubpage

PoolChunk負責內存的分配與釋放,其內部最小的分配單元爲page,page的默認大小爲8k。如果我們申請很多小塊內存時,都按照page來分配,那麼資源浪費可不是一點半點。針對這個問題,netty將page拆成了更小的內存塊element,但超出了PoolChunk的負責範圍,此時netty使用PoolSubpage來解決這個問題。

PoolSubpage(PoolSubpage<T> head, PoolChunk<T> chunk, int memoryMapIdx, int runOffset, int pageSize, int elemSize) {
        this.chunk = chunk;
        this.memoryMapIdx = memoryMapIdx;
        this.runOffset = runOffset;
        this.pageSize = pageSize;
        bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64
        init(head, elemSize);
    }

無非是賦值,我們來看下bitmap數組大小爲什麼是pageSize>>10;從註釋中可以看出,16B是elementSize最小值,64是long的位數,也就是說用bitmap的位個數可以滿足分配最多element時的數目。bitmap大小爲什麼不使用pageSize/elemenSize/64?因爲申請最大空間後,對於後面可回收後重新分配該對象,不需要重新開闢空間,只需重新調用init()賦值。

head是之前調用arena.findSubpagePoolHead(normCapacity)的返回值。可以從函數調用中分析出,該方法要不是在構造函數中調用,或者在subpage回收後重新分配時調用。

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);
    }

先把標誌位doNoDestroy設爲true,表示該page在使用中,不能被清除。然後賦值。計算出實際使用的bitmapLength大小。並把標誌位清零(因爲之前可能用過有垃圾數據,僅需把當前要用的部分清零)。

chunk在分配空間時,大小8k以下的空間交給subPage管理,然而chunk併爲將subPage暴露給外面,於是subPage通過addToPool()方法,將自己加入到chunk.arean的pool中。

    private void addToPool(PoolSubpage<T> head) {
        assert prev == null && next == null;
        prev = head;
        next = head.next;
        next.prev = this;
        head.next = this;
    }

僅僅是鏈表插入操作,將當前節點插入到head之後。

回到chunk中分配小於8k以下部分的方法中

 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();

我們來看下subpage.allocate();

 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);
    }

如果此時沒有可分配的element,那麼直接返回。getNetxAvail()無非找到當前page中可分配的段的下標,然後在bitmap上標記該段被使用。如果該page所有可用element分配完了後,將這個subpage從pool中刪除。

我們可以看到這裏在subPage這個數據結構上進行“管理分配”,返回一個element的index。toHandle操作無非在返回的long上標記可用空間。

    private long toHandle(int bitmapIdx) {
        return 0x4000000000000000L | (long) bitmapIdx << 32 | memoryMapIdx;
    }

一個long,其中低32位用來表示memoryMapIdx,無非是執行分配的具體page,高32位中的低6位表示bitmap,即定位到當前page中可分配的段。

我們來看下getNextAvail方法。

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

直接從成員中取出下一個可用的位置,如果nextAvail>=0,要麼是剛初始化,或者是有element重新回收未分配的,直接返回,並把其標記爲-1,否則繼續調用findNextAvail。

    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;
    }

沒有明確指定nextAvail位置時,於是從頭到尾查。先定位到bitmaps中的哪個long沒有分配完。

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;
    }

於是找那個long上的哪個位未分配,於是構造val返回。val就是最低6位表示該long上的哪個字節,其他位即val >> 6後記錄的是哪個long,即bitmap的下標。其實val就是bitmapIdx,它定位到具體的某個element。

關於上一篇遺留問題,總結一下。

poolChunk的allocate函數返回一個long類型的handle,其中當handle<Integer.MAX_VALUE時,它表示chunk的節點id,當handle>Integer.MAX_VALUE,他分配的是一個Subpage,節點id=memoryMapIdx, 且能得到Subpage的bitmapIdx。

再來看下poolChunk對應的小於8k的free過程。

        int bitmapIdx = bitmapIdx(handle);

        if (bitmapIdx != 0) { // free a subpage
            PoolSubpage<T> subpage = subpages[subpageIdx(memoryMapIdx)];
            assert subpage != null && subpage.doNotDestroy;

            // 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(subpage.elemSize);
            synchronized (head) {
                if (subpage.free(head, bitmapIdx & 0x3FFFFFFF)) {
                    return;
                }
            }
        }

先根據handle取到bitmapIdx,再取出對應的subpage,找到根據elemSize取出head,加鎖(應爲涉及到head爲頭的鏈表的改變),再調用subpage.free。

boolean free(PoolSubpage<T> head, int bitmapIdx) {
        if (elemSize == 0) {
            return true;
        }
        int q = bitmapIdx >>> 6;
        int r = bitmapIdx & 63;
        assert (bitmap[q] >>> r & 1) != 0;
        bitmap[q] ^= 1L << r;

        setNextAvail(bitmapIdx);

        if (numAvail ++ == 0) {
            addToPool(head);
            return true;
        }

        if (numAvail != maxNumElems) {
            return true;
        } else {
            // Subpage not in use (numAvail == maxNumElems)
            if (prev == next) {
                // Do not remove if this subpage is the only one left in the pool.
                return true;
            }

            // Remove this subpage from the pool if there are other subpages left in the pool.
            doNotDestroy = false;
            removeFromPool();
            return false;
        }
    }
無非在bitmap上標記爲1可用,然後將當前bitmapIdx設置到nextAvail,如果當前subpage的可用大小爲0,那麼把可用大小加1,並且添加到pool中,即head爲頭的雙向鏈表中。如果添加後numAvail==maxNumElems說明該page上所有element都未分配。如果該subpage是鏈上的唯一一塊那麼不處理(這樣做儘可能的保證arena分配小內存時能直接從pool中取,而不用再到chunk中去獲取),否則將其從arean的pool的鏈表中剔除。


連着寫了兩篇,netty內存池的結構越來越清晰了,當然問題還有不少,比如分配大小大於一整個chunk後如何處理,subpage的elemSize存在很多可能值,handle具體用處。一步步探索吧。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章