PoolSubpage源码解析&思考

77 阅读4分钟

Netty版本:4.1.38

作用&使用场景

  1. 一个pageSize默认是8k,仅对一些场景分配8k,太过浪费;
  2. PoolSubpage来进行精细化分配小内存场景
  3. PoolSubpage是为了更好的管理subpage而服务的
  4. 一个PoolSubpage 实际上对应一个pageSize(8k)

思维导图

image.png

整体流程图

初始化&申请流程图

image.png

源码解析

init

这个的作用是为了初始化PoolArena中的tinySubpagePools数组、smallSubpagePools数组的第一个元素,

PoolSubpage(int pageSize) {
    chunk = null;
    memoryMapIdx = -1;
    runOffset = -1;
    elemSize = -1;
    this.pageSize = pageSize;
    bitmap = null;
}

这个是申请内存、真正调用的初始化方法

PoolSubpage(PoolSubpage<T> head, PoolChunk<T> chunk, int memoryMapIdx, int runOffset, int pageSize, int elemSize) {
    this.chunk = chunk; // 哪块chunk
    this.memoryMapIdx = memoryMapIdx; // 对应二叉搜索树里面的索引
    this.runOffset = runOffset; // 代表PoolSubpage对象的内存地址
    this.pageSize = pageSize; // 默认8k 
    // bitmap中每一个bit位都代表一个subpage的elemSize大小;
    bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64 
    init(head, elemSize);
}

void init(PoolSubpage<T> head, int elemSize) {
    doNotDestroy = true;
    this.elemSize = elemSize;
    if (elemSize != 0) {
        // 获取当前可用的(最大的)元素个数
        maxNumElems = numAvail = pageSize / elemSize;
        nextAvail = 0;
        // 获取当前所需要的bitmap的数组长度; 所以这里就看到了 bitmapLength != bitmap.length
        // 这里就有冗余了,高版本(比如:4.1.112)做了优化;
        // 这里就代表只需要多少个long类型的数字就可以表示
        bitmapLength = maxNumElems >>> 6;
        if ((maxNumElems & 63) != 0) {
            bitmapLength ++;
        }

        for (int i = 0; i < bitmapLength; i ++) {
            bitmap[i] = 0;
        }
    }
    // 将当前的PoolSubpage对象添加到tinySubpagePools 或者smallSubpagePools的链表中;
    addToPool(head);
}
allocate
long allocate() {
    if (elemSize == 0) {
        return toHandle(0);
    }

    if (numAvail == 0 || !doNotDestroy) {
        return -1;
    }

    // 获取当前bitmapIdx指针,有两方面组成,大于64,表示bitmap数组的索引位,小于64的数字表示bitmap[i]中的第几位bit;
    // 比如:elemSize = 32k, bitmapIdx = 67 ==> bitmap[1] = 4;也就是说索引位是1的bitmap数字的第4位bit对应的subpage已经被占用了;
    
    final int bitmapIdx = getNextAvail();
    int q = bitmapIdx >>> 6;
    int r = bitmapIdx & 63;
    assert (bitmap[q] >>> r & 1) == 0;
    // 分配完成,将该bit位标记为1
    bitmap[q] |= 1L << r;

     // 如果PoolSubpage被占用完了,就将其移除
    if (-- numAvail == 0) {
        removeFromPool();
    }

    return toHandle(bitmapIdx);
}

private int getNextAvail() {
    int nextAvail = this.nextAvail;
    // nextAvail =0 ,表示首次分配;
    // nextAvail>0 ,表示有一些subpage被free,这里在重用;所以直接取就行;
    if (nextAvail >= 0) {
        this.nextAvail = -1;
        return nextAvail;
    }
    // 这里就是最常用获取subpage的过程
    return findNextAvail();
}

private int findNextAvail() {
    final long[] bitmap = this.bitmap;
    final int bitmapLength = this.bitmapLength;
    // 1、 先遍历bitmap数组
    for (int i = 0; i < bitmapLength; i ++) {
        long bits = bitmap[i];

        if (~bits != 0) {
            // 只有bitmap[i] 中还有空余bit位不等于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类型的位数(long有64个字节)
    for (int j = 0; j < 64; j ++) {
    // 找到第一个bit位=0
        if ((bits & 1) == 0) {
        // 相或,然后返回
            int val = baseVal | j;
            if (val < maxNumElems) {
                return val;
            } else {
                break;
            }
        }
        // 如果对应的bits为=1, 就将bit右移1位;
        bits >>>= 1;
    }
    return -1;
}

内存释放的流程图

image.png

源码解析

boolean free(PoolSubpage<T> head, int bitmapIdx) {
    if (elemSize == 0) {
        return true;
    }
    // 从bitmapIdx 还原索引位,bit位;
    int q = bitmapIdx >>> 6;
    int r = bitmapIdx & 63;
    assert (bitmap[q] >>> r & 1) != 0;
    bitmap[q] ^= 1L << r;

    // 这里 和getNextAvail方法里面的nextAvail相呼应了;
    setNextAvail(bitmapIdx);

    // 这里和allocate中的numAvail==0--> removeFromPool相呼应
    if (numAvail ++ == 0) {
        addToPool(head);
        return true;
    }

    if (numAvail != maxNumElems) {
        return true;
    } else {
        // Subpage not in use (numAvail == maxNumElems)
        if (prev == next) {
            // Do not remove if this subpage is the only one left in the pool.
            return true;
        }

        // Remove this subpage from the pool if there are other subpages left in the pool.
        doNotDestroy = false;
        // 如果PoolSubpage中的subpage都被回收了,那么就可以考虑将其回收到PoolChunk,提供给其他人用
        removeFromPool();
        return false;
    }
}

高低版本针对bitmap的优化

4.1.38版本中的PoolSubpage

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;
    // 根据pageSize(默认8k)简单粗暴,右移10位,
    // 为何右移10位,这是由于低版本的最低内存单位是16B, 每个long类型是64位
    // 这么做是有冗余申请的;因为bitmap中的每一位都代表着elemSize大小;
    // 所以在高版本就给优化了
    bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64
    init(head, elemSize);
}

4.1.112版本的Netty中的PoolSubpage

PoolSubpage(PoolSubpage<T> head, PoolChunk<T> chunk, int pageShifts, int runOffset, int runSize, int elemSize) {

    ......
    // 获取当前能够保存多少个elemSize的元素
    maxNumElems = numAvail = runSize / elemSize;
    // 获取最大可保存元素后,在除以64,表示bitmap中的每一位都代表elemSize大小
    int bitmapLength = maxNumElems >>> 6;
    // 如果有余数,则bitmapLength ++;
    if ((maxNumElems & 63) != 0) {
        bitmapLength ++;
    }
    this.bitmapLength = bitmapLength;
    bitmap = new long[bitmapLength];
    nextAvail = 0;

    lock = null;
    addToPool(head);
}

学习&总结

1、 位运算

  1. 速度快,就需要极致的考究,最贴近计算机语言,才能达到极致的速度;
  2. handle指针:一个long类型数据,根据高低位不同,代表不同的含义;一方面可以节省内存,另一方面又可以保障操作的原子性;这一点和ForkJoinPool中的ctl等变量有异曲同工之妙

2、大而化小,动态回收、分配内存

大块操作虽然简单,但是浪费空间; 所以针对小内存的场景,可以根据subpage来动态分配;

  1. 如果PoolSubpage已经全部分配完,那可以从smallSubpagePools或者tinySubpagePools中移除了;简化操作;
  2. 如果PoolSubpage中的某些subpage被free,同样再给添加回来;在进行管理;如果PoolSubpage中的subpage都回收了,那可以将这个Subpage对应的page页归还给PoolChunk;

参看网址