Netty源碼分析:PoolSubpage

Netty源碼分析:PoolSubpage

在上篇介紹Netty源碼分析:PoolChunk的博文中,我們分析了allocateSubpage方法(如下)的前半部分,後半部分是藉助於PoolSubpage來完成的。這篇博文就介紹下PoolSubpage這個類。

    private long allocateSubpage(int normCapacity) {
        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>(this, id, runOffset(id), pageSize, normCapacity);
            subpages[subpageIdx] = subpage;
        } else {
            subpage.init(normCapacity);
        }
        return subpage.allocate();
    }

1、PoolSubpage類的屬性和構造函數

    final class PoolSubpage<T> {

        final PoolChunk<T> chunk;//用來表示該Page屬於哪個Chunk
        private final int memoryMapIdx;//用來表示該Page在Chunk.memoryMap中的索引
        // 當前Page在chunk.memoryMap的偏移量
        private final int runOffset;
        private final int pageSize;//Page的大小,默認爲8192
        /*
        long 類型的數組bitmap用來表示Page中存儲區域的使用狀態,
        數組中每個long的每一位表示一個塊存儲區域的佔用情況:0表示未佔用,1表示佔用。
        例如:對於一個4K的Page來說如果這個Page用來分配1K的存儲與區,
        那麼long數組中就只有一個long類型的元素且這個數值的低4危用來指示4個存儲區域的佔用情況。
        */
        private final long[] bitmap;
        //PoolSubpage本身設計爲一個鏈表結構
        PoolSubpage<T> prev;
        PoolSubpage<T> next;

        boolean doNotDestroy;
        int elemSize;//塊的大小
        private int maxNumElems;//page按elemSize大小分得的塊個數
        private int bitmapLength;//bitmap數組實際會用到的長度,等於pageSize/elemSize/64
        // 下一個可用的位置
        private int nextAvail;
        // 可用的段數量
        private int numAvail;

        PoolSubpage(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,這裏的16指的是塊的最小值,64是long類型的所佔的bit數。
            init(elemSize);
        } 

如在Netty源碼分析:PoolChunk的博文中介紹的:

對於小於一個Page的內存,Netty在Page中完成分配。每個Page會被切分成大小相同的多個存儲塊,存儲塊的大小由第一次申請的內存塊大小決定。對於Page的大小爲4K,第一次申請的時1K,則這個Page就會被分成4個存儲塊。

一個Page只能用於分配與第一次申請時大小相同的內存,例如,一個4K的Page,如果第一次分配了1K的內存,那麼後面這個Page就只能繼續分配1K的內存,如果有一個申請2K內存的請求,就需要在一個新的Page中進行分配。

Page中存儲區域的使用狀態通過一個long數組來維護,數組中每個long的每一位表示一個塊存儲區域的佔用情況:0表示未佔用,1表示佔用。例如:對於一個4K的Page來說如果這個Page用來分配1K的存儲與區,那麼long數組中就只有一個long類型的元素且這個數值的低4危用來指示4個存儲區域的佔用情況。

回到如上所示的關於PoolSubpage的字段和構造函數的代碼中:

在PoolSubpage的實現中,使用的是字段private final long[] bitmap;來記錄Page的使用狀態,其中bitmap數組的最大長度爲:pageSize / 16 / 64,這裏的16指的是塊的最小值,64是long類型的所佔的bit數。

而存儲塊的大小使用的是字段elemSize來記錄,當第一次在這個page上申請小於pageSize的內存時將調用如下的init函數來記錄相關的塊信息,例如:塊的大小、塊的個數、初始化bitmap等。

    void init(int elemSize) {
        doNotDestroy = true;
        this.elemSize = elemSize;
        if (elemSize != 0) {
            maxNumElems = numAvail = pageSize / elemSize;
            nextAvail = 0;
            bitmapLength = maxNumElems >>> 6;//等價於bitmapLength = maxNumElems / 64;64爲long類型所佔的bit數 
            if ((maxNumElems & 63) != 0) { //如果塊的個數不是64的整倍數,則加 1
                bitmapLength ++;
            }

            for (int i = 0; i < bitmapLength; i ++) {
                bitmap[i] = 0;
            }
        }

        addToPool();
    }

繼續看addToPool()方法

該方法的功能爲:將當前的Page加入到PoolArena所持有的PoolSubpage<T>[] tinySubpagePools;PoolSubpage<T>[] smallSubpagePools數組中,爲什麼要加入到這裏面來呢??

這是因爲PoolSubpage[] tinySubpagePools,數組默認長度爲32(512 >>4),這裏面存儲的Page是專門用來分配小內存tiny(小於512),smallSubpagePools數組中存儲的Page則是用來分配small(大於等於512小於pageSize)內存的。

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

2、subpage.allocate()

    /**
     * Returns the bitmap index of the subpage allocation.
     */
    long allocate() {
        if (elemSize == 0) {
            return toHandle(0);
        }
        //判斷此page是否還有“塊”可用,以及是否被銷燬了,如果沒有可用空間或者是被銷燬了則返回-1.
        if (numAvail == 0 || !doNotDestroy) {
            return -1;
        }

        final int bitmapIdx = getNextAvail();
        int q = bitmapIdx >>> 6;
        int r = bitmapIdx & 63;
        assert (bitmap[q] >>> r & 1) == 0;//此bit位此時應該爲0.
        bitmap[q] |= 1L << r;//將bitmap[q]這個long型的數的第rbit置爲1,標識此“塊”已經被分配。

        if (-- numAvail == 0) {
            removeFromPool();
        }

        return toHandle(bitmapIdx);
    }

該函數主要邏輯爲:

1、首先通過getNextAvail()方法來得到此Page中下一個可用“塊”的位置bitmapIdx。至於如何來得到,稍後將分析。

2、將bitmapIdx“可用塊”在bitmap中標識爲“已佔用”的狀態。具體如何來做的呢?

2.1)、根據q = bitmapIdx >>> 6r = bitmapIdx & 63兩行代碼得到第bitmapIdx這個可用“內存塊”在bitmap標識數組中是第q個long元素且是第q個元素的第r爲來進行標識的。例如:假設bitmapIdx=66,則q=1,r=2,即是用bitmap[1]這個long類型數的第2個bit位來表示此“內存塊”的。

2.2)、利用assert (bitmap[q] >>> r & 1) == 0判斷分配前bitmap[q]第r(bit)位一定是0,0:未佔用。由於馬上就將此“內存塊”分配出去,因此利用bitmap[q] |= 1L << r將bitmap[q]第r(bit)位置爲1,1:佔用。

3、將page的可用“塊數”numAvail減一,減一之後如果結果爲0,則表示此Page的內存無可分配的了,因此,將其從Arena所持有的鏈表中移除。

下面來看下getNextAvail()方法是如何得到此Page中下一個可用“塊”的位置bitmapIdx的?

先猜測一下:對標識數組bitmap的每一個long元素的每一位進行判斷,看是否爲0,0表示爲佔用。

    private int getNextAvail() {
        int nextAvail = this.nextAvail;

        if (nextAvail >= 0) {//page被第一次申請可用“塊”的時候nextAvail=0,會直接返回。表示直接用第0位內存塊
            this.nextAvail = -1;
            return nextAvail;
        }
        return findNextAvail();
    }

如果是第2、3…來申請時,則會調用如下的findNextAvail()來實現。下面的代碼比較簡單,和猜測的一樣,確實是通過按順序遍歷判斷標識數組bitmap的每一個long元素的每一個bit是否爲零來得到。不過下面的代碼有一點值得我們學習:將位操作應用的淋漓盡致。

    private int findNextAvail() {
        final long[] bitmap = this.bitmap;
        final int bitmapLength = this.bitmapLength;
        //對標識數組bitmap中的每一個long元素進行判斷
        for (int i = 0; i < bitmapLength; i ++) {
            long bits = bitmap[i];
            if (~bits != 0) {//還存在至少1個bit位不爲1,1表示佔用
                return findNextAvail0(i, bits);
            }
        }
        return -1;
    }  

    private int findNextAvail0(int i, long bits) {
        final int maxNumElems = this.maxNumElems;
        final int baseVal = i << 6;
        //對long類型的數的每一bit位進行判斷。
        for (int j = 0; j < 64; j ++) {
            if ((bits & 1) == 0) { //第j(bit)爲0,即爲佔用
                int val = baseVal | j;
                if (val < maxNumElems) {
                    return val;
                } else {
                    break;
                }
            }
            bits >>>= 1;
        }
        return -1;
    } 

小結

分析完PoolSubpage這個類,我們需要了解3點:

1、PoolSubpage這個類的塊大小是由第一次申請的內存大小來決定的。

2、PoolSubpage這個類是通過long類型的數組bitmap來對PoolSubPage中的每個塊的使用情況進行標識的。

3、如果想在某個PoolSubpage分配一個小於pageSize的內存,則首先是通過按順序遍歷標識數組bitmap中每個long元素中的每一bit位中爲0的位置。

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