Netty内存池之PoolSubPage

本文深入探讨Netty内存池中的PoolSubPage,当内存申请小于8k时,PoolChunk通过PoolSubPage进行分配。PoolSubPage利用平衡二叉树和bitmap数组来标记和管理内存,最小单位为16字节。它将内存块切分为多个部分,并通过bitmap记录状态,从而高效地进行内存分配和释放。

在前一篇中刚刚分析了PoolChunk的内存管理策略,简单回顾下PoolChunk的分配原则:
1) 平衡二叉树维护标记内存块的使用情况
2) > 8k, PoolChunk直接在二叉树上标记并分配内存
3) < 8k,在PoolChunk的叶子节点进行相应标记,并交粒度更细的分配工作交给PoolSubPage处理
本文接着PoolChunk中的内存申请<8k的线路分析PoolSubPage的内存分配和释放流程

private long allocateSubpage(int normCapacity) {
    int d = maxOrder;
    int id = allocateNode(d);//在chunk的叶子节点标记
    if (id < 0) {
        return id;
    }
    final PoolSubpage<T>[] subpages = this.subpages;
    final int pageSize = this.pageSize;
    freeBytes -= pageSize;
    int subpageIdx = subpageIdx(id);//找到叶子节点对应的subpage索引
    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();
}

回顾PoolChunk中分配规则,当申请内存小于8k时,有allocatSubPage分配,首先标记二叉树中可用的叶子节点,找出与与叶子节点subpage在数组中的位置,如果数组为空,创建subpage对象;接着调用subpage的allocat()方法来分配

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
    init(elemSize);
}

初始化相应成员之后,调用init(int)方法。
关于bitmap数组说明,PoolSubPage利于bitmap数组来标记内存的使用情况。在Netty内存池管理中,内存最小划分单位为16的字节,那么对于pageSize大小的内存块包含的最小内存块为pageSize/16;当用long类型(64位)的数组bitmap记录内存的使用情况时;PoolSubpage中的bitmap采用long类型的每一位表示一段16字节内存段,因此bitmap数组最大长度为pageSize/16/64即可记录pageSize的内存分配状态。

void init(int elemSize) {
    doNotDestroy = true;
    this.elemSize = elemSize;
    if (elemSize != 0) {
        maxNumElems = numAvail = pageSize / elemSize;//按申请内存大小将pageSize划分
        nextAvail = 0;
        bitmapLength = maxNumElems >>> 6;  //bitmap中描述内存块的长度
        if ((maxNumElems & 63) != 0) {
            bitmapLength ++;
        }
        for (int i = 0; i < bitmapLength; i ++) {
            bitmap[i] = 0;
        }
    }
    addToPool(); //加入环形链表缓存
}

在init(int)中根据请求内存大小,将PoolChunk叶节点pageSize大小内存块切分位maxNumElems块,而故只需要maxNumElems/64或其+1长度的bitmap即可描述其内存占用状态;然后将PoolsubPage纳入内存池管理

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

addToPool即将PoolSubpage加入到环形链表中
在subpage初始化完成之后,调用subpage的allocate()

long allocate() {
    if (elemSize == 0) {
        return toHandle(0);   
    }
    if (numAvail == 0 || !doNotDestroy) {//无法分配
        return -1;
    }
    final int bitmapIdx = getNextAvail();//获取可用段
    int q = bitmapIdx >>> 6;   //bitmap数组中的位置
    int r = bitmapIdx & 63;    //数组中在long中位的位置
    assert (bitmap[q] >>> r & 1) == 0;
    bitmap[q] |= 1L << r;      //将数组中long的对于位标记
    if (-- numAvail == 0) {    //可用段减1
        removeFromPool();      //移除subpage
    }
    return toHandle(bitmapIdx);
}

在allocate中,进行参数检查之后,获取bitmap中可以分配的索引段

private int getNextAvail() {
    int nextAvail = this.nextAvail;
    if (nextAvail >= 0) {
        this.nextAvail = -1;
        return nextAvail;
    }
    return findNextAvail();
}

除第一次查找直接返会0,以后均会调用findNextAvail()搜索可用段

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) {  //bits位不全为0,可分配
            return findNextAvail0(i, bits);
        }
    }
    return -1;
}

当数组bitmap[i]中数组64位不全为0,即数组索引i中管理内存段可分配,调用findNextAvail(i,bits);

    final int maxNumElems = this.maxNumElems;
    final int baseVal = i << 6;   //基准位置段
    for (int j = 0; j < 64; j ++) { //从第1位到64位
        if ((bits & 1) == 0) {  //第(j+1) 位不为0,可分配
            int val = baseVal | j;  // 加偏移段 
            if (val < maxNumElems) {  //确定位置
                return val;
            } else {
                break;
            }
        }
        bits >>>= 1;
    }
    return -1;
}

在bitmap可用数组元素中,基准位置段为64*i(数组下标),偏移段为首位=0的位,基准段与偏移段相加即为最终可分配内存段位置
而可分配位置bitmapIdx上,对于bitmap的数组元素下表q =bitmapIdx/64,
该元素中r位(bitmapIdx&63)记录,故将r为标记为不可用1;bitmap[q]|=1L<

boolean free(int bitmapIdx) {
    if (elemSize == 0) {
        return true;
    }
    int q = bitmapIdx >>> 6;//bitmap[q]管理该段内存
    int r = bitmapIdx & 63;
    assert (bitmap[q] >>> r & 1) != 0;  //内存已被分配
    bitmap[q] ^= 1L << r;   //清除分配标记
    setNextAvail(bitmapIdx);   //bitmapIdx标记为可用
    if (numAvail ++ == 0) {    //可用段=0
        addToPool();           //将PoolSubPage释放至环形链表
        return true;
    }
    if (numAvail != maxNumElems) { //可用段 没达到最大可以上限
        return true;
    } else {
        if (prev == next) {         //SubPage完全未被分配
            return true;
        }
        doNotDestroy = false;
        removeFromPool();
        return false;
    }
}

至此,PoolSubPage内存管理规则介绍完毕,它与PoolChunk配合分配与管理不同量级的内存分配与释放

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值