吃透Netty源码系列二十三之池化内存分配四

PoolSubpage的allocate

继续上一篇的,现在执行到subpage.allocate(),里面才是对子页可分配的内存做了记录并分配了位图的一位,并返回一个64位的句柄handle,前面说要解释这个句柄到底做什么的,现在就开始吧。
老规矩,看源码,我来解释:

 long allocate() {
        if (elemSize == 0) {
            return toHandle(0);//一般不会申请0内存,为什么这里还要处理,暂时没弄明白
        }

        if (numAvail == 0 || !doNotDestroy) {//没有可用的容量或者要销毁
            return -1;
        }
//获取下一个可用的位图索引,不是位图数组
        final int bitmapIdx = getNextAvail();
        int q = bitmapIdx >>> 6;//获取所在的位图在数组中的索引
        int r = bitmapIdx & 63;//获取64余数,0-63 位图中的索引信息 也就是第几位要设置为1
        assert (bitmap[q] >>> r & 1) == 0;//根据位图索引获取这个位图中的位置是0表示可用
        bitmap[q] |= 1L << r;//将可用的位图索引设置为1,即不可用

        if (-- numAvail == 0) {//如果没有可用了,就从链表中删除
            removeFromPool();
        }

        return toHandle(bitmapIdx);
    }

getNextAvail

这个就是获取下一个位图的索引bitmapIdx ,这里的索引是32位是由两部分组成的:
在这里插入图片描述
这个是怎么算的,来看看具体源码吧:
this.nextAvail最开始是0,会直接返回0,并且把this.nextAvail设置为-1,这样第二次就要进行findNextAvail了,这样确实第一次就不需要运算了,用一个判断来提升性能。

 private int getNextAvail() {
        int nextAvail = this.nextAvail;
        if (nextAvail >= 0) {
            this.nextAvail = -1;
            return nextAvail;//第一次直接返回,后面就要findNextAvail
        }
        return findNextAvail();//-1表示要找
    }

findNextAvail

bitmapLength就是实际上用到的位图数,所以不需要整个位图数据来遍历,只需要遍历bitmapLength个位图。
把每个位图取出来按位取反,如果不为0,说明没取反前还有位置有0存在,这个位图还能用,否则就说明所有位都是1了,取反就等于0,不可用了,如果还能用,就要看用哪一位findNextAvail0,默认是从低位开始的。如果都不能用就返回-1

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

findNextAvail0

这里是真正使用位图的时候,先看传入参数,传入的i其实就是位图所在位图数组的索引,bits就是位图本身。首先会有i << 6,这个就是先求出bitmapIdx26位高位的值baseVal ,就是位图索引对应的值,然后遍历位图,j表示的就是位图里的第j位,也就是bitmapIdx的低6位,而且是从低位开始判断,取值是0-63。然后(bits & 1)取出最低位,如果是0,表示可用分配,然后就将baseVal | j,表示高26位和低6位合并了,这样才算是完整的bitmapIdx。而且这样计算出来的bitmapIdx刚好也是已经分配的内存数-1,所以最后还要和最大可分配数比较,小于才能分配,因为等于就已经超出最大可分配数maxNumElems ,因为bitmapIdx是从0开始的,最大应该是maxNumElems -1。如果低位不可分配就将位图右移一位,也就是取出第二位继续判断。

 private int findNextAvail0(int i, long bits) {
        final int maxNumElems = this.maxNumElems;//最大可分配数
        final int baseVal = i << 6;//要分配的起始索引,根据第i个位图,如果是0表示0-63进行分配 1表示64-127分配
//遍历位图的每一位,从最低位开始遍历,0表示没有过
        for (int j = 0; j < 64; j ++) {//j表示位图里第j位,从低位到高位0-63
            if ((bits & 1) == 0) {//取出最低位,为0表示可用
                int val = baseVal | j;//加上位置序号j后的新的索引值,比如开始是0,第一个+0索引就是0,然后+1,索引1,类似最后索引是baseVal+63
                if (val < maxNumElems) {//如果索引没到最大可分配数就返回,其实最大索引就是maxNumElems-1
                    return val;
                } else {
                    break;//等于就不行了,跳出循环
                }
            }
            bits >>>= 1;//位图右移,即从低位往高位
        }
        return -1;
    }

举个例子,比如我申请的内存是32B,那么总共需要的位图应该是8k/32/64=4个,最大可分配数maxNumElems=8k/32=256,这里的这4个位图,每一个刚好对应一个内存分配,总共256个。现在32B的内存来了,首先拿出第0个位图,也就是i=0,发现可用,于是就开始0<<<6=0,高26位全是0了,再看位图的64位情况,发现底0位就可以用,与是就返回了,这个bitmapIdx就是0。同样的,假设第0个位图用满了,如果我又来了一个32B,那就需要用第1个位图,最后返回的bitmapIdx就是1000000

我们继续,看看返回后做了什么。
首先是bitmapIdx >>> 6,得到高26位,获取位图在位图数组的索引。
然后bitmapIdx & 63,得到低6位,,获取位图内部索引。
最后进行设置bitmap[q] |= 1L << r,将这个位图内部索引所对应的值设置为1,表示不可用了。

用我上面举的例子,画了一个bitmapIdx的变化图:
在这里插入图片描述

removeFromPool

如果发现可用的内存数量numAvail0了,就会将这个页从双向循环链表中删除。

    private void removeFromPool() {
        assert prev != null && next != null;
        prev.next = next;
        next.prev = prev;
        next = null;
        prev = null;
    }

toHandle重点以及为什么要加上0x4000000000000000L

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

这个也是关键点,首先他会把bitmapIdx结合内存映射索引memoryMapIdx一起转化为64位数。
(long) bitmapIdx << 32 | memoryMapIdx32位是内存映射索引memoryMapIdx,高32中的低6位是所在位图的内部索引,占据64位的位图中的0-63位的某一位,其余26位就是位图所在位图数组的索引。
而最后加上0x4000000000000000L,开始没想明白,后来发现了,因为不想让高32位是0,如果是第一次分配,bitmapIdx是0,memoryMapIdx=2048,高32位就是0,返回就是2048,这样不会被当做子页分配来处理。我们看下面的方法:
在这里插入图片描述
在这里插入图片描述
可以看到allocateRun是分配8k以及以上的内存,可以直接返回内存映射索引的,也可以是2048,而allocateSubpage通过toHandle方法,如果没有加上0x4000000000000000L,第一次分配bitmapIdx=0,返回的也是2048。这样在后面的initBuf方法就会出问题:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面的方法中,对于2048来说,取出的bitmapIdx都是0,因此子页分配的就不会调用initBufWithSubpage,会发现内部的chunk偏移是不对的,少了子页的内部偏移,就会出问题。
所以为了识别子页的bitmapIdx,最高非符号位设置了1。这里可能还有问题,如果bitmapIdx26位里面,刚好跟他或起来进位了,又变成0了,这个不用担心,第一bitmapIdx26位前面说了,是位图数组的索引,初始化的时候对于最小尺寸16B已经创建好了,数组长度就是8,而且最高位符号位也不可能是1呀,是正数呀,不会进位变成0

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值