Netty源码分析:PoolSubpage

Posted HelloWorld_EE

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty源码分析:PoolSubpage相关的知识,希望对你有一定的参考价值。

Netty源码分析:PoolSubpage

在上篇介绍Netty源码分析:PoolChunk的博文中,我们分析了allocateSubpage方法(如下)的前半部分,后半部分是借助于PoolSubpage来完成的。这篇博文就介绍下PoolSubpage这个类。

    private long allocateSubpage(int normCapacity) 
        int d = maxOrder; // subpages are only be allocated from pages i.e., leaves
        int id = allocateNode(d);
        if (id < 0) 
            return id;
        

        final PoolSubpage<T>[] subpages = this.subpages;
        final int pageSize = this.pageSize;

        freeBytes -= pageSize;

        int subpageIdx = subpageIdx(id);
        PoolSubpage<T> subpage = subpages[subpageIdx];
        if (subpage == null) 
            subpage = new PoolSubpage<T>(this, id, runOffset(id), pageSize, normCapacity);
            subpages[subpageIdx] = subpage;
         else 
            subpage.init(normCapacity);
        
        return subpage.allocate();
    

1、PoolSubpage类的属性和构造函数

    final class PoolSubpage<T> 

        final PoolChunk<T> chunk;//用来表示该Page属于哪个Chunk
        private final int memoryMapIdx;//用来表示该Page在Chunk.memoryMap中的索引
        // 当前Page在chunk.memoryMap的偏移量
        private final int runOffset;
        private final int pageSize;//Page的大小,默认为8192
        /*
        long 类型的数组bitmap用来表示Page中存储区域的使用状态,
        数组中每个long的每一位表示一个块存储区域的占用情况:0表示未占用,1表示占用。
        例如:对于一个4K的Page来说如果这个Page用来分配1K的存储与区,
        那么long数组中就只有一个long类型的元素且这个数值的低4危用来指示4个存储区域的占用情况。
        */
        private final long[] bitmap;
        //PoolSubpage本身设计为一个链表结构
        PoolSubpage<T> prev;
        PoolSubpage<T> next;

        boolean doNotDestroy;
        int elemSize;//块的大小
        private int maxNumElems;//page按elemSize大小分得的块个数
        private int bitmapLength;//bitmap数组实际会用到的长度,等于pageSize/elemSize/64
        // 下一个可用的位置
        private int nextAvail;
        // 可用的段数量
        private int numAvail;

        PoolSubpage(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,这里的16指的是块的最小值,64是long类型的所占的bit数。
            init(elemSize);
         

如在Netty源码分析:PoolChunk的博文中介绍的:

对于小于一个Page的内存,Netty在Page中完成分配。每个Page会被切分成大小相同的多个存储块,存储块的大小由第一次申请的内存块大小决定。对于Page的大小为4K,第一次申请的时1K,则这个Page就会被分成4个存储块。

一个Page只能用于分配与第一次申请时大小相同的内存,例如,一个4K的Page,如果第一次分配了1K的内存,那么后面这个Page就只能继续分配1K的内存,如果有一个申请2K内存的请求,就需要在一个新的Page中进行分配。

Page中存储区域的使用状态通过一个long数组来维护,数组中每个long的每一位表示一个块存储区域的占用情况:0表示未占用,1表示占用。例如:对于一个4K的Page来说如果这个Page用来分配1K的存储与区,那么long数组中就只有一个long类型的元素且这个数值的低4危用来指示4个存储区域的占用情况。

回到如上所示的关于PoolSubpage的字段和构造函数的代码中:

在PoolSubpage的实现中,使用的是字段private final long[] bitmap;来记录Page的使用状态,其中bitmap数组的最大长度为:pageSize / 16 / 64,这里的16指的是块的最小值,64是long类型的所占的bit数。

而存储块的大小使用的是字段elemSize来记录,当第一次在这个page上申请小于pageSize的内存时将调用如下的init函数来记录相关的块信息,例如:块的大小、块的个数、初始化bitmap等。

    void init(int elemSize) 
        doNotDestroy = true;
        this.elemSize = elemSize;
        if (elemSize != 0) 
            maxNumElems = numAvail = pageSize / elemSize;
            nextAvail = 0;
            bitmapLength = maxNumElems >>> 6;//等价于bitmapLength = maxNumElems / 64;64为long类型所占的bit数 
            if ((maxNumElems & 63) != 0)  //如果块的个数不是64的整倍数,则加 1
                bitmapLength ++;
            

            for (int i = 0; i < bitmapLength; i ++) 
                bitmap[i] = 0;
            
        

        addToPool();
    

继续看addToPool()方法

该方法的功能为:将当前的Page加入到PoolArena所持有的PoolSubpage<T>[] tinySubpagePools;PoolSubpage<T>[] smallSubpagePools数组中,为什么要加入到这里面来呢??

这是因为PoolSubpage[] tinySubpagePools,数组默认长度为32(512 >>4),这里面存储的Page是专门用来分配小内存tiny(小于512),smallSubpagePools数组中存储的Page则是用来分配small(大于等于512小于pageSize)内存的。

    private void addToPool() 
        PoolSubpage<T> head = chunk.arena.findSubpagePoolHead(elemSize);
        assert prev == null && next == null;
        prev = head;
        next = head.next;
        next.prev = this;
        head.next = this;
     

2、subpage.allocate()

    /**
     * Returns the bitmap index of the subpage allocation.
     */
    long allocate() 
        if (elemSize == 0) 
            return toHandle(0);
        
        //判断此page是否还有“块”可用,以及是否被销毁了,如果没有可用空间或者是被销毁了则返回-1.
        if (numAvail == 0 || !doNotDestroy) 
            return -1;
        

        final int bitmapIdx = getNextAvail();
        int q = bitmapIdx >>> 6;
        int r = bitmapIdx & 63;
        assert (bitmap[q] >>> r & 1) == 0;//此bit位此时应该为0.
        bitmap[q] |= 1L << r;//将bitmap[q]这个long型的数的第rbit置为1,标识此“块”已经被分配。

        if (-- numAvail == 0) 
            removeFromPool();
        

        return toHandle(bitmapIdx);
    

该函数主要逻辑为:

1、首先通过getNextAvail()方法来得到此Page中下一个可用“块”的位置bitmapIdx。至于如何来得到,稍后将分析。

2、将bitmapIdx“可用块”在bitmap中标识为“已占用”的状态。具体如何来做的呢?

2.1)、根据q = bitmapIdx >>> 6r = bitmapIdx & 63两行代码得到第bitmapIdx这个可用“内存块”在bitmap标识数组中是第q个long元素且是第q个元素的第r为来进行标识的。例如:假设bitmapIdx=66,则q=1,r=2,即是用bitmap[1]这个long类型数的第2个bit位来表示此“内存块”的。

2.2)、利用assert (bitmap[q] >>> r & 1) == 0判断分配前bitmap[q]第r(bit)位一定是0,0:未占用。由于马上就将此“内存块”分配出去,因此利用bitmap[q] |= 1L << r将bitmap[q]第r(bit)位置为1,1:占用。

3、将page的可用“块数”numAvail减一,减一之后如果结果为0,则表示此Page的内存无可分配的了,因此,将其从Arena所持有的链表中移除。

下面来看下getNextAvail()方法是如何得到此Page中下一个可用“块”的位置bitmapIdx的?

先猜测一下:对标识数组bitmap的每一个long元素的每一位进行判断,看是否为0,0表示为占用。

    private int getNextAvail() 
        int nextAvail = this.nextAvail;

        if (nextAvail >= 0) //page被第一次申请可用“块”的时候nextAvail=0,会直接返回。表示直接用第0位内存块
            this.nextAvail = -1;
            return nextAvail;
        
        return findNextAvail();
    

如果是第2、3…来申请时,则会调用如下的findNextAvail()来实现。下面的代码比较简单,和猜测的一样,确实是通过按顺序遍历判断标识数组bitmap的每一个long元素的每一个bit是否为零来得到。不过下面的代码有一点值得我们学习:将位操作应用的淋漓尽致。

    private int findNextAvail() 
        final long[] bitmap = this.bitmap;
        final int bitmapLength = this.bitmapLength;
        //对标识数组bitmap中的每一个long元素进行判断
        for (int i = 0; i < bitmapLength; i ++) 
            long bits = bitmap[i];
            if (~bits != 0) //还存在至少1个bit位不为1,1表示占用
                return findNextAvail0(i, bits);
            
        
        return -1;
      

    private int findNextAvail0(int i, long bits) 
        final int maxNumElems = this.maxNumElems;
        final int baseVal = i << 6;
        //对long类型的数的每一bit位进行判断。
        for (int j = 0; j < 64; j ++) 
            if ((bits & 1) == 0)  //第j(bit)为0,即为占用
                int val = baseVal | j;
                if (val < maxNumElems) 
                    return val;
                 else 
                    break;
                
            
            bits >>>= 1;
        
        return -1;
     

小结

分析完PoolSubpage这个类,我们需要了解3点:

1、PoolSubpage这个类的块大小是由第一次申请的内存大小来决定的。

2、PoolSubpage这个类是通过long类型的数组bitmap来对PoolSubPage中的每个块的使用情况进行标识的。

3、如果想在某个PoolSubpage分配一个小于pageSize的内存,则首先是通过按顺序遍历标识数组bitmap中每个long元素中的每一bit位中为0的位置。

以上是关于Netty源码分析:PoolSubpage的主要内容,如果未能解决你的问题,请参考以下文章

nettybuffer源码学习2

netty里的ByteBuf扩容源码分析

Netty源码分析(七) PoolChunk

源码分析Netty4专栏

源码分析Netty4专栏

Netty-源码分析LineBasedFrameDecoder