netty内存--PoolChunk分析

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

PoolChunk使用了jemalloc分配算法。对这个算法不了解的,请另行百度哈,这里我就不展开了。

首先说下几个概念吧。page是chunk中内存分配的最小单元,chunk是由一系列的page组成的。当然,page也可以分割成一系列的subpage。一个chunk的大小chunksize=2{maxorder}*pageSize。

PoolChunk是由final修饰的,这代表不能去修改的哈。

先看下PoolChunk的成员变量咯

    final PoolArena<T> arena;//chunk所属的arena
    final T memory;//拥有的内存块
    final boolean unpooled;//是否非池化
    final int offset;//偏移量

    private final byte[] memoryMap;//各个page分配的二叉树
    private final byte[] depthMap;//高度二叉树
    private final PoolSubpage<T>[] subpages;//subpage的节点数组
    /** Used to determine if the requested capacity is equal to or greater than pageSize. */
    private final int subpageOverflowMask;//判断分配的请求是subpage
    private final int pageSize;//页大小 默认为8K
    private final int pageShifts;//1左移多少位 == pageSize  默认为13 即 1 << 13 =8k
    private final int maxOrder;//最大的高度 默认 11
    private final int chunkSize;//块大小 默认 16MB
    private final int log2ChunkSize;//log2(chunkSize) 默认24
    private final int maxSubpageAllocs;//可分配的最大节点数 默认 1<<11= 2048 
    /** Used to mark memory as unusable */
    private final byte unusable;//标记节点为不可用  maxOrder +1 默认12

    private int freeBytes;//可分配的字节数

    PoolChunkList<T> parent;//poolChunkList使用,双向链表
    PoolChunk<T> prev;
    PoolChunk<T> next;

 该类有两个构造函数,一个用于普通初始化,另一个用于Huge分配请求。

我们这就只关注普通初始化,上代码

    PoolChunk(PoolArena<T> arena, T memory, int pageSize, int maxOrder, int pageShifts, int chunkSize, int offset) {
        unpooled = false;
        this.arena = arena;
        this.memory = memory;
        this.pageSize = pageSize;
        this.pageShifts = pageShifts;
        this.maxOrder = maxOrder;
        this.chunkSize = chunkSize;
        this.offset = offset;

        unusable = (byte) (maxOrder + 1);//不可用标记
        log2ChunkSize = log2(chunkSize);
        subpageOverflowMask = ~(pageSize - 1);//判断是否是subpage请求的标志  
        freeBytes = chunkSize;//可分配的字节数

        assert maxOrder < 30 : "maxOrder should be < 30, but is: " + maxOrder;
        maxSubpageAllocs = 1 << maxOrder;//subpage最大分配个数


        //初始化数据
        // Generate the memory map.
        memoryMap = new byte[maxSubpageAllocs << 1];
        depthMap = new byte[memoryMap.length];
        int memoryMapIndex = 1;
        for (int d = 0; d <= maxOrder; ++ d) { // move down the tree one level at a time
            int depth = 1 << d;
            for (int p = 0; p < depth; ++ p) {
                // in each level traverse left to right and set value to the depth of subtree
                memoryMap[memoryMapIndex] = (byte) d;
                depthMap[memoryMapIndex] = (byte) d;
                memoryMapIndex ++;
            }
        }

        subpages = newSubpageArray(maxSubpageAllocs);
    }

 构造方法没什么好看的,无非就是给各个属性赋值,在给memoryMap 和depthMap初始化各个值。

下面看下一个核心的方法long allocate(int normCapacity)

    //首先判断要分配的空间大小是否 >= pageSize
    if ((normCapacity & subpageOverflowMask) != 0) { 
            return allocateRun(normCapacity);//走正常分配流程
        } else {
            return allocateSubpage(normCapacity);//分配subpage
        }

 这个方法疑惑的地方应该就是(normCapacity & subpageOverflowMask) != 0这个判断了。因为netty追求性能,所以把位运算算是运用到了极致。之前再构造方法里看了  subpageOverflowMask = ~(pageSize-1),按默认的来说 即subpageOverflowMask = ~(8192-1) = -8972;而-8192 & 小于 8972的数都是为0 的,缺少画图工具,图就不划了,可以自己敲代码验证一下。

long allocateRun(int normCapacity)

    //该方法至少会分配一个pageSize的内存
     private long allocateRun(int normCapacity) {
        //首先计算出所需要的节点的高度
        int d = maxOrder - (log2(normCapacity) - pageShifts);
        //在该层找到能分配的节点
        int id = allocateNode(d);
        //没找到
        if (id < 0) {
            return id;
        }
        //计算剩余能分配的字节数
        freeBytes -= runLength(id);
        return id;
    }

 private int allocateNode(int d)

     private int allocateNode(int d) {
        int id = 1;
        //和subpageOverflowMask作用相当,所有小于 d 的值 &initial 都等于0
        int initial = - (1 << d); 
        //memoryMap[id]
        byte val = value(id);
        //表示没有满足条件的节点
        if (val > d) { // unusable
            return -1;
        }
        //子节点可满足田间
        while (val < d || (id & initial) == 0) { // id & initial == 1 << d for all ids at depth d, for < d it is 0
            //左移一位,进入子节点
            id <<= 1;
            val = value(id);
            if (val > d) {//左节点不满足
                id ^= 1;//右节点
                val = value(id);
            }
        }
        byte value = value(id);
        assert value == d && (id & initial) == 1 << d : String.format("val = %d, id & initial = %d, d = %d",
                value, id & initial, d);
        //把符合条件的节点标记位不可用  memoryMap[id] = unusable;
        setValue(id, unusable); // mark as unusable
        //更新父节点的分配信息
        updateParentsAlloc(id);
        return id;
    }

 private void updateParentsAlloc(int id)

         while (id > 1) {    
            //取父节点
            int parentId = id >>> 1;
            byte val1 = value(id);//获取该节点的值
            byte val2 = value(id ^ 1);//获取该节点的兄弟节点的值
            byte val = val1 < val2 ? val1 : val2;//获取较小的值
            setValue(parentId, val);//把较小的值付给父节点
            id = parentId;//递归更新
        }

 下面分析分配的空间 <pageSize的情况

  allocateSubpage(normCapacity)

        //找到subpage的头节点
        PoolSubpage<T> head = arena.findSubpagePoolHead(normCapacity);
        //加锁,因为在分配的过程中会修改链表的结构
        synchronized (head) {
            //subpage只能在最高的chunk里分配
            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;
            //获取subpage的偏移索引
            int subpageIdx = subpageIdx(id);
            PoolSubpage<T> subpage = subpages[subpageIdx];
            if (subpage == null) {
                subpage = new PoolSubpage<T>(head, this, id, runOffset(id), pageSize, normCapacity);
                subpages[subpageIdx] = subpage;
            } else {
                subpage.init(head, normCapacity);
            }
            return subpage.allocate();
        }

 int subpageIdx(int memoryMapIdx)

 return memoryMapIdx ^ maxSubpageAllocs;

移除高位的值,保留地位的值,默认来说,= memoryMapIdx-maxSubpageAllocs

最后再来说说内存的释放过程,

主要的方法为void free(long handle)

    void free(long handle) {
        //取低位 32位获取节点
        int memoryMapIdx = memoryMapIdx(handle);
        //取高位32位回去subpage
        int bitmapIdx = bitmapIdx(handle);
        //释放subpage
        if (bitmapIdx != 0) { // free a subpage
            PoolSubpage<T> subpage = subpages[subpageIdx(memoryMapIdx)];
            assert subpage != null && subpage.doNotDestroy;

            // Obtain the head of the PoolSubPage pool that is owned by the PoolArena and synchronize on it.
            // This is need as we may add it back and so alter the linked-list structure.
            PoolSubpage<T> head = arena.findSubpagePoolHead(subpage.elemSize);
            synchronized (head) {
                if (subpage.free(head, bitmapIdx & 0x3FFFFFFF)) {
                    return;
                }
            }
        }
        freeBytes += runLength(memoryMapIdx);//更新空用字节
        setValue(memoryMapIdx, depth(memoryMapIdx));//还原节点的高度值
        updateParentsFree(memoryMapIdx);//更新父节点
    }

至此,poolchunk就差不多讲完了。

猜你喜欢

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