美文网首页
源码干货|Netty 源码解析:PoolSubpage 的实现原

源码干货|Netty 源码解析:PoolSubpage 的实现原

作者: JAVA炭烧 | 来源:发表于2021-04-26 10:56 被阅读0次

    本文分享PoolSubpage如何管理Small内存块。点赞再看,养成习惯!

    源码分析基于Netty 4.1.52

    内存管理算法

    PoolSubpage负责管理Small内存块。一个PoolSubpage中的内存块size都相同,该size对应SizeClasses#sizeClasses表格的一个索引index。

    新创建的PoolSubpage都必须加入到PoolArena#smallSubpagePools[index]的链表中。

    PoolArena#smallSubpagePools是一个PoolSubpage数组,数组中每个元素都是一个PoolSubpage链表,PoolSubpage之间可以通过next,prev组成链表。

    感兴趣的同学可以参考《内存对齐类SizeClasses》。

    注意,Small内存size并不一定小于pageSize(默认为8K)

    默认Small内存size <= 28672(28KB)

    关于Normal内存块,Small内存块,pageSize,可参考《PoolChunk实现原理》。

    PoolSubpage实际上就是PoolChunk中的一个Normal内存块,大小为其管理的内存块size与pageSize最小公倍数。

    PoolSubpage使用位图的方式管理内存块。

    PoolSubpage#bitmap是一个long数组,其中每个long元素上每个bit位都可以代表一个内存块是否使用。

    内存分配

    分配Small内存块有两个步骤

    1.PoolChunk中分配PoolSubpage。

    如果PoolArena#smallSubpagePools中已经有对应的PoolSubpage缓冲,则不需要该步骤。

    2.PoolSubpage上分配内存块

    PoolChunk#allocateSubpage

    private long allocateSubpage(int sizeIdx) {
        // #1
        PoolSubpage<T> head = arena.findSubpagePoolHead(sizeIdx);
        synchronized (head) {
            //allocate a new run
            // #2
            int runSize = calculateRunSize(sizeIdx);
            //runSize must be multiples of pageSize
            // #3
            long runHandle = allocateRun(runSize);
            if (runHandle < 0) {
                return -1;
            }
            // #4
            int runOffset = runOffset(runHandle);
            int elemSize = arena.sizeIdx2size(sizeIdx);
    
            PoolSubpage<T> subpage = new PoolSubpage<T>(head, this, pageShifts, runOffset,
                               runSize(pageShifts, runHandle), elemSize);
    
            subpages[runOffset] = subpage;
            // #5
            return subpage.allocate();
        }
    }
    

    1 这里涉及修改PoolArena#smallSubpagePools的PoolSubpage链表,需要同步操作

    2 计算内存块size和pageSize最小公倍数

    3 分配一个Normal内存块,作为PoolSubpage的底层内存块,大小为Small内存块size和pageSize最小公倍数

    4 构建PoolSubpage

    runOffset,即Normal内存块偏移量,也是该PoolSubpage在整个Chunk中的偏移量

    elemSize,Small内存块size

    5 在subpage上分配内存块

    PoolSubpage(PoolSubpage<T> head, PoolChunk<T> chunk, int pageShifts, int runOffset, int runSize, int elemSize) {
        // #1
        this.chunk = chunk;
        this.pageShifts = pageShifts;
        this.runOffset = runOffset;
        this.runSize = runSize;
        this.elemSize = elemSize;
        bitmap = new long[runSize >>> 6 + LOG2_QUANTUM]; // runSize / 64 / QUANTUM
        init(head, elemSize);
    }
    
    void init(PoolSubpage<T> head, int elemSize) {
        doNotDestroy = true;
        if (elemSize != 0) {
            // #2
            maxNumElems = numAvail = runSize / elemSize;
            nextAvail = 0;
            bitmapLength = maxNumElems >>> 6;
            if ((maxNumElems & 63) != 0) {
                bitmapLength ++;
            }
    
            for (int i = 0; i < bitmapLength; i ++) {
                bitmap[i] = 0;
            }
        }
        // #3
        addToPool(head);
    }
    

    1.bitmap长度为runSize / 64 / QUANTUM,从《内存对齐类SizeClasses》可以看到,runSize都是2^LOG2_QUANTUM的倍数。

    2.elemSize:每个内存块的大小

    maxNumElems:内存块数量

    bitmapLength:bitmap使用的long元素个数,使用bitmap中一部分元素足以管理全部内存块。

    (maxNumElems & 63) != 0 ,代表maxNumElems不能整除64,所以bitmapLength要加1,用于管理余下的内存块。

    3.添加到PoolSubpage链表中
    前面分析《Netty内存池与PoolArena》中说过,在 PoolArena 中分配Small内存块时,首先会从PoolArena#smallSubpagePools中查找对应的PoolSubpage。如果找到了,直接从该PoolSubpage上分配内存。否则,分配一个Normal内存块,创建 PoolSubpage ,再在上面分配内存块。

    PoolSubpage#allocate

    long allocate() {
        // #1
        if (numAvail == 0 || !doNotDestroy) {
            return -1;
        }
        // #2
        final int bitmapIdx = getNextAvail();
        // #3
        int q = bitmapIdx >>> 6;
        int r = bitmapIdx & 63;
        assert (bitmap[q] >>> r & 1) == 0;
        bitmap[q] |= 1L << r;
        // #4
        if (-- numAvail == 0) {
            removeFromPool();
        }
        // #5
        return toHandle(bitmapIdx);
    }
    

    1.没有可用内存块,分配失败。通常PoolSubpage分配完成后会从PoolArena#smallSubpagePools中移除,不再在该PoolSubpage上分配内存,所以一般不会出现这种场景。

    2.获取下一个可用内存块的bit下标

    3. 设置对应bit为1,即已使用

    bitmapIdx >>> 6 ,获取该内存块在bitmap数组中第q元素

    bitmapIdx & 63 ,获取该内存块是bitmap数组中第q个元素的第r个bit位

    bitmap[q] |= 1L << r ,将bitmap数组中第q个元素的第r个bit位设置为1,表示已经使用

    4 所有内存块已分配了,则将其从PoolArena中移除。

    5. toHandle 转换为最终的handle

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

    nextAvail为初始值或free时释放的值。

    如果nextAvail存在,设置为不可用后直接返回该值。

    如果不存在,调用findNextAvail查找下一个可用内存块。

    private int findNextAvail() {
        final long[] bitmap = this.bitmap;
        final int bitmapLength = this.bitmapLength;
        // #1
        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;
    
        // #2
        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. 遍历bitmap, ~bits != 0 ,表示存在一个bit位不为1,即存在可用内存块。

    2. 遍历64个bit位,

    (bits & 1) == 0 ,检查最低bit位是否为0(可用),为0则返回val。

    val等于 (i << 6) | j ,即 i * 64 + j ,该bit位在bitmap中是第几个bit位。

    bits >>>= 1 ,右移一位,处理下一个bit位。

    内存释放

    释放Small内存块可能有两个步骤:

    1.释放 PoolSubpage的上内存块

    2.如果PoolSubpage中的内存块已全部释放,则从Chunk中释放该PoolSubpage,同时从PoolArena#smallSubpagePools移除它。

    PoolSubpage#free

    boolean free(PoolSubpage<T> head, int bitmapIdx) {
        if (elemSize == 0) {
            return true;
        }
        // #1
        int q = bitmapIdx >>> 6;
        int r = bitmapIdx & 63;
        assert (bitmap[q] >>> r & 1) != 0;
        bitmap[q] ^= 1L << r;
    
        setNextAvail(bitmapIdx);
        // #2
        if (numAvail ++ == 0) {
            addToPool(head);
            return true;
        }
    
        // #3
        if (numAvail != maxNumElems) {
            return true;
        } else {
            // #4
            if (prev == next) {
                // Do not remove if this subpage is the only one left in the pool.
                return true;
            }
    
            // #5
            doNotDestroy = false;
            removeFromPool();
            return false;
        }
    }
    

    1. 将对应bit位设置为可以使用

    2. 在PoolSubpage的内存块全部被使用时,释放了某个内存块,这时重新加入到PoolArena中。

    3. 未完全释放,即还存在已分配内存块,返回true

    4. 逻辑到这里,是处理所有内存块已经完全释放的场景。

    PoolArena#smallSubpagePools链表组成双向链表,链表中只有head和当前PoolSubpage时, 当前 PoolSubpage的prev,next都指向head。

    这时 当前 PoolSubpage是PoolArena中该链表最后一个PoolSubpage,不释放该PoolSubpage,以便下次申请内存时直接从该PoolSubpage上分配。

    5. 从PoolArena中移除,并返回false,这时PoolChunk会将释放对应Page节点。

    
    void free(long handle, int normCapacity, ByteBuffer nioBuffer) {
        if (isSubpage(handle)) {
            // #1
            int sizeIdx = arena.size2SizeIdx(normCapacity);
            PoolSubpage<T> head = arena.findSubpagePoolHead(sizeIdx);
    
            PoolSubpage<T> subpage = subpages[runOffset(handle)];
            assert subpage != null && subpage.doNotDestroy;
    
            synchronized (head) {
                // #2
                if (subpage.free(head, bitmapIdx(handle))) {
                    //the subpage is still used, do not free it
                    return;
                }
            }
        }
    
        // #3
        ...
    }
    

    1
    查找head节点,同步

    2
    调用subpage#free释放Small内存块

    如果subpage#free返回false,将继续向下执行,这时会释放PoolSubpage整个内存块,否则,不释放PoolSubpage内存块。

    3
    释放Normal内存块,就是释放PoolSubpage整个内存块。该部分内容可参考《PoolChunk实现原理》。

    码字不易,如果觉得本篇文章对你有用的话,请给我点赞!关注作者,后续会有更多的干货分享,请持续关注!

    相关文章

      网友评论

          本文标题:源码干货|Netty 源码解析:PoolSubpage 的实现原

          本文链接:https://www.haomeiwen.com/subject/kxvsrltx.html