Netty内存池之PoolArea

关于Netty内存池管理已经介绍了PoolChunk以及PoolSubPage来管理内存池。然后从应用层调用来看,应用层序并未直接去调用PoolChunk或PoolSubPage的allocate/free方法去管理内存,当我们通过PooledByteBufAllocator去分配操作内存时,它将内存的管理职责委托给PoolArea,本文将介绍PoolArea是如何管理内存的。
其成员变量如下:

static final int numTinySubpagePools = 512 >>> 4;  //tiny缓存数量

final PooledByteBufAllocator parent;   //分配器

private final int maxOrder;       //平衡二叉树最大层数
final int pageSize;               //pageSize大小
final int pageShifts;             //pageSize
final int chunkSize;              //chunk块大小
final int subpageOverflowMask;    //判断是否<8k
final int numSmallSubpagePools;   //small subpage缓存数量
private final PoolSubpage<T>[] tinySubpagePools; //tiny subpage缓存数组
private final PoolSubpage<T>[] smallSubpagePools; //small subpage缓存数组

private final PoolChunkList<T> q050;    //基于PoolChunk链表
private final PoolChunkList<T> q025;
private final PoolChunkList<T> q000;
private final PoolChunkList<T> qInit;
private final PoolChunkList<T> q075;
private final PoolChunkList<T> q100;

在PoolArea成员变量中,包含基于PoolChunk的链表,以及PoolSubPage的数组。此外,在PoolArea中对PoolSubPage内存的分配又分为两类
1)>512byte 对应smallSubpagePools
2)<512byte 对应tinySubPagePools

protected PoolArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
    this.parent = parent;
    this.pageSize = pageSize;
    this.maxOrder = maxOrder;
    this.pageShifts = pageShifts;
    this.chunkSize = chunkSize;
    subpageOverflowMask = ~(pageSize - 1);
    tinySubpagePools = newSubpagePoolArray(numTinySubpagePools);
    for (int i = 0; i < tinySubpagePools.length; i ++) {
        tinySubpagePools[i] = newSubpagePoolHead(pageSize);
    }
    numSmallSubpagePools = pageShifts - 9;
    smallSubpagePools = newSubpagePoolArray(numSmallSubpagePools);
    for (int i = 0; i < smallSubpagePools.length; i ++) {
        smallSubpagePools[i] = newSubpagePoolHead(pageSize);
    }
    q100 = new PoolChunkList<T>(this, null, 100, Integer.MAX_VALUE);
    q075 = new PoolChunkList<T>(this, q100, 75, 100);
    q050 = new PoolChunkList<T>(this, q075, 50, 100);
    q025 = new PoolChunkList<T>(this, q050, 25, 75);
    q000 = new PoolChunkList<T>(this, q025, 1, 50);
    qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25);
    q100.prevList = q075;
    q075.prevList = q050;
    q050.prevList = q025;
    q025.prevList = q000;
    q000.prevList = null;
    qInit.prevList = qInit;
}

在构造器中,初始化tinySubpagePool与smallSubpagePool数组,以及q100等5个PoolChunkList,并将这5个PoolChunkList在连接为链表,如下图所示:
这里写图片描述
1)qInit:存储内存使用率0-25%的chunk
2)q000:存储内存使用率1-50%的chunk
3)q025:存储内存使用率25-75%的chunk
4)q050:存储内存使用率50-100%个chunk
5)q075:存储内存使用率75-100%个chunk
6)q100:存储内存使用率100%chunk
构造器初始化完之后,接下来关注PoolArea的分配方法

PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
    PooledByteBuf<T> buf = newByteBuf(maxCapacity);
    allocate(cache, buf, reqCapacity);
    return buf;
}

在上面的分配方法中,newByeteBuf(int)为抽象方法,由子类(基于directArea以及heapArea的PoolArea)具体实现,接着进入内存标记管理环节

private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
    final int normCapacity = normalizeCapacity(reqCapacity); //内存对齐
    if (isTinyOrSmall(normCapacity)) { // capacity < pageSize
        int tableIdx;
        PoolSubpage<T>[] table;
        if (isTiny(normCapacity)) { // < 512 byte
            if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {  //从线程缓存分配
                return;
            }
            tableIdx = tinyIdx(normCapacity);
            table = tinySubpagePools;
        } else {                   //   > 512byte &&   < 8K
        if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {   //从线程缓存分配
                return;
            }
            tableIdx = smallIdx(normCapacity);
            table = smallSubpagePools;
        }
        synchronized (this) {  //从PoolSubPage数组分配
            final PoolSubpage<T> head = table[tableIdx];
            final PoolSubpage<T> s = head.next;
            if (s != head) {
                assert s.doNotDestroy && s.elemSize == normCapacity;
                long handle = s.allocate();
                assert handle >= 0;
                s.chunk.initBufWithSubpage(buf, handle, reqCapacity);
                return;
            }
        }
    } else if (normCapacity <= chunkSize) {  // < chunkSize
        if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {  //从线程缓存分配
            return;
        }
    } else {   //  > chunkSize
        allocateHuge(buf, reqCapacity);  //直接分配  
        return;
    }
    allocateNormal(buf, reqCapacity, normCapacity);  //从平衡二叉树分配
}

在申请内存时,经过内存对齐算法计算之后申请内存的大小特点如下:
1)当申请大小>chunkSize,实际申请大小等于初始申请大小
2)当申请大小>512byte,实际申请大小等于512的整数倍中最小而又大于或等于申请大小的值,如申请大小513 对应1024、4010对应4096
3)当申请大小小于<512byte时,申请大小为16的整数倍中最小而又大于或等于申请大小的值,如申请7对应16,55对应64;

在进行内存对齐之后,PoolArea将内存的申请划分为4类;
1) <512byte,由tinyPoolSubPage相应资源分配
2) 大于等于512byte且小于pageSize,由smallPoolSubPage相应资源分配
3) 大于等于pageSize且小于等于chunkSize,由PoolChunk在平衡二叉树上分配
4) >chunkSize,直接分配不缓存
同时,在PoolSubPage(smart和tiny两种)时分配时的原则大致如下:在对应线程缓存分配,成功直接返回,失败则在对应数组缓存数组分配。而PoolChunk时分配时,原则类似,先在线程缓存分配,成功立即返回,失败则Chunk维护的平衡二叉树上进行分配。此外当从PoolSubPage数组中分配失败是,将从PoolChunk平衡二叉树上分配。关于本地线程缓存分配,之后文章PoolThradCache将会详细分析。
PoolSubPage数组的分配委托给PoolChunk中的initBufWithSubPage分配,而PoolChunk上平衡而二叉树的分配如下方法:

private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
    if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
        q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
        q075.allocate(buf, reqCapacity, normCapacity) || q100.allocate(buf, reqCapacity, normCapacity)) {
        return;
    }
    PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
    long handle = c.allocate(normCapacity);
    assert handle > 0;
    c.initBuf(buf, handle, reqCapacity);
    qInit.add(c);
}

在其分配过程,首先会尝试去构造器初始化PoolChunkList链表中分配,其分配顺序顺序为q050->q025->q000->qInit->q075->q100中分配,其分配顺序基于以上顺序原因:
1)优先在内存利用率50%-100%的内存块分配应为该区域内存利用率高,提高系统内存利用率;
2)最低优先级在75%及以上内存块分配,以提高内存分配成功率
初始分配时,PoolChunkList无法分配,会直接基于PoolChunk进行分配,并申请到的chunk加入qInit中。当PoolChunk进入PoolChunkList之后,会根据当前chunk内存利用率和当前所在PoolChunkList指定的利用情况,可能进行移动。例如在q050中的chunk块,内存使用率超过75%时,该chunk加从q050转移至q075,内存使用率效益25%时,将由q050转移至q025
关于PoolArea如何释放内存方法如下:

void free(PoolChunk<T> chunk, long handle, int normCapacity, boolean sameThreads) {
    if (chunk.unpooled) { //chunk未缓存,直接回收
        destroyChunk(chunk);
    } else {
        if (sameThreads) {  //在同一线程中,将分配内存添加至线程缓存
            PoolThreadCache cache = parent.threadCache.get();
            if (cache.add(this, chunk, handle, normCapacity)) {
                // cached so not free it.
                return;
            }
        }
        synchronized (this) { //不在同一线程
            chunk.parent.free(chunk, handle); //PoolChunkList负责释放
        }
    }
}

至此,PoolArea内存管理流程大致介绍完毕。而PoolArea作为抽象内定义的一些列抽象方法,由内部类DirectArea(直接内存)与HeapArea(堆内存)完成特定实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值