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具体用处。一步步探索吧。




猜你喜欢

转载自blog.csdn.net/panxj856856/article/details/80386735