netty内存-PoolSubpage分析

版权声明:转载请注明原链接 https://blog.csdn.net/lij231/article/details/82773388

前面讲过PoolChunk最小得分配单位是page,而page得默认大小是8k,但在实际应用当中,会有很多的小数据,如果小数据也占用一个page的话。那无疑内存将会大大的浪费。针对这种情况,netty就建了个新东西叫PoolSubpage。PoolSubpage的工作和PoolChunk类似,只是PoolSubpage所管理的内存块远小于PoolChunk,且PoolSubpage的内存是从chunk的最深那一层分割的。

首先,看下PoolSubpage的字段

    final PoolChunk<T> chunk;//所属的poolChunk
    private final int memoryMapIdx;//poolchunk的id
    private final int runOffset;//当前page在chunk里的偏移量
    private final int pageSize;//page大小
    private final long[] bitmap;//各个poolsubpage的使用状态

    PoolSubpage<T> prev;//前节点
    PoolSubpage<T> next;

    boolean doNotDestroy;//是否能清除
    int elemSize;//把page分割的大小
    private int maxNumElems;//总共有多少个elem
    private int bitmapLength;//bitmap需要用到的长度
    private int nextAvail;//下一个可用节点位置
    private int numAvail;//可用节点的数量

 PollSubPage的字段就只有这些,需要特别注意的就是bitmap,是个long类型的数组,long是64位,每一位都代表多应的节点是否可用。

构造方法

     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呢?那是因为用户请求分配的大小在内存分配这条链上已经被normalizeCapacity处理了,这个方法在PoolArena中,最小值是16,而long类型的长度是16,代表一个long类型的数据能表示64个节点是否已经被使用,所以要完整的表示所有的节点数是否被使用 所需要的 long个数= pageSzie /16 / 64 =pageSize/(2^4*2^6)=pageSzie /2^10 =pageSize >>>10。

void init(PoolSubpage<T> head, int elemSize)

   //从IDE里可以看到,这个方法会在两个地方调用,1.初始化;2.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;//得到bitmap的长度  相当于 / 64
            if ((maxNumElems & 63) != 0) {当个数小于64得话,maxNumElems >>> 6=0,所以需要补偿
                bitmapLength ++;
            }

            for (int i = 0; i < bitmapLength; i ++) {//把所有节点置为没有使用状态
                bitmap[i] = 0;
            }
        }
        //加入到池里,之所以要加入到池里是因为chunk分配了subPage,但并没有把subPage暴露出去,所以需要自己把自己加入到chunk.arena里,以便管理
        addToPool(head);
    }

构造方法和字段说完,下面来看看PoolSubPage的核心方法,

long allocate()

     long allocate() {
        if (elemSize == 0) {
            return toHandle(0);
        }
        //没有可用节点或已经被销毁了
        if (numAvail == 0 || !doNotDestroy) {
            return -1;
        }
        //获取到可用节点
        final int bitmapIdx = getNextAvail();
        //算出可用节点在bitmap里的索引
        int q = bitmapIdx >>> 6;
        //计算出long值得第几位表示这个索引
        int r = bitmapIdx & 63;
        assert (bitmap[q] >>> r & 1) == 0;//没被使用校验
        bitmap[q] |= 1L << r;//把long对应得位数置为1

        if (-- numAvail == 0) {//把可用节点数减一,等于0表示没有可用内存,就把subpage从arena里删除
            removeFromPool();
        }
        //返回获取到得内存索引,是一个long类型,其中高32位代表chunk得id,低32位代表subpage得id
        return toHandle(bitmapIdx);
    }

 接下来看下是如何寻找节点得

    private int getNextAvail() {
        int nextAvail = this.nextAvail;
        if (nextAvail >= 0) {//下一节点可用,直接返回
            this.nextAvail = -1;
            return nextAvail;
        }
        return findNextAvail();
    }

    private int findNextAvail() {
        final long[] bitmap = this.bitmap;
        final int bitmapLength = this.bitmapLength;
        for (int i = 0; i < bitmapLength; i ++) {//对bitmap遍历
            long bits = bitmap[i];
            if (~bits != 0) {//取非,不等于0说明该long值上有位数是=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) {//等于0说明该位置还没有被分配
                int val = baseVal | j;//得出在所有的节点里,这是第几个
                if (val < maxNumElems) {//小于最大节点数量,就返回这个节点
                    return val;
                } else {
                    break;
                }
            }
            bits >>>= 1;
        }
        return -1;
    }

到这里,PoolSubPage的分配内存,初始化就讲完了,除了一些位运算有点饶人外,其他的还是比较简单的。

最后看看PoolSubPage是怎么释放内存的

boolean free(PoolSubpage<T> head, int bitmapIdx)

        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);
        //numAvail==0说明之前已经把subpage从arena里删除了,这里重新加入
        if (numAvail ++ == 0) {
            addToPool(head);
            return true;
        }
        
        if (numAvail != maxNumElems) {
            return true;
        } else {//下面一整段是做了下性能处理,
            if (prev == next) {
       
                return true;
            }

       
            doNotDestroy = false;
            removeFromPool();
            return false;
        }
    }

 -----------------------------------------------------------------------

百世经纶,扑街之路,不由分说

猜你喜欢

转载自blog.csdn.net/lij231/article/details/82773388