2023-09-13  阅读(2)
原文作者:https://blog.csdn.net/wangwei19871103/category_9681495_2.html 原文地址: https://blog.csdn.net/wangwei19871103/article/details/104356566

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位是由两部分组成的:

202309132202055001.png
这个是怎么算的,来看看具体源码吧:
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的变化图:

202309132202060292.png

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,这样不会被当做子页分配来处理。我们看下面的方法:

202309132202066943.png

202309132202077134.png
可以看到allocateRun是分配8k以及以上的内存,可以直接返回内存映射索引的,也可以是2048,而allocateSubpage通过toHandle方法,如果没有加上0x4000000000000000L,第一次分配bitmapIdx=0,返回的也是2048。这样在后面的initBuf方法就会出问题:

202309132202087295.png

202309132202102516.png

202309132202114547.png

202309132202120698.png
上面的方法中,对于2048来说,取出的bitmapIdx都是0,因此子页分配的就不会调用initBufWithSubpage,会发现内部的chunk偏移是不对的,少了子页的内部偏移,就会出问题。
所以为了识别子页的bitmapIdx,最高非符号位设置了1。这里可能还有问题,如果bitmapIdx26位里面,刚好跟他或起来进位了,又变成0了,这个不用担心,第一bitmapIdx26位前面说了,是位图数组的索引,初始化的时候对于最小尺寸16B已经创建好了,数组长度就是8,而且最高位符号位也不可能是1呀,是正数呀,不会进位变成0

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


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文