netty PoolChunk内存分配一

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhaozhenzuo/article/details/75578730
目的:
申请的内存可重用,比如直接内存。
netty抽象了一个poolChunk,用于管理申请的物理内存。一个PollChunk默认16M,但是申请内存时有可能小于这个值,因此netty将poolChunk进行切分。分成若干个subPage。默认一个subPage大小为8k,即存在2048个subPage。
netty为了方便管理内存,将内存逻辑上分成不同大小的块,然后用一棵平衡二叉树进行管理。叶子结点是实际可分配的subPage结点。
每个父结点可以管理分配的内存为两个子结点的内存和。比如根结点(下标为1)可以管理两个子结点2和3总共16M内存。


一.默认大小
一个poolChunk默认16m。
由2048个page构成,每个page默认8k。

二.数据结构:
物理内存DirectBuf,内存管理数组,subPage叶子结点。

内存管理数组用于管理哪些块已经分配,是一个完全二叉树,如下图:
可以看到,11层是叶子结点,2048的父结点是1024结点。1024结点表示管理16k的内存。
如果当前结点的标记值等于当前所处树深度值时,表示可分配。
比如:1024结点标记为:10,则表示可以分配它下面的两个子结点2048及2049共16k内存。
如果1024结点值为11,则表示其子结点已分配,子结点同层的其它结点还有可分配的结点。
如果1024结点标记为:12(总层数+1),则表示其下面所有子结点都已分配。

总结下来每个结点的标记状态有以下三个值:
1.标记状态等于当前层次,则表示当前结点可分配内存
2.标记状态等于当前层次+1,则表示当前结点下的部分子结点已分配
3.标记状态等于最大层次+1,则表示当前结点下的所有子结点都已分配

三.分配流程
假设分配大小为16k,最大层次为11,则流程如下:
1.定位到对应层次,这里为10层第一个结点1024
2.判断当前结点对应内存管理数组的标记状态,如果等于10层则说明可分配
3.当前可分配的情况下,更新当前结点为:12,表示当前结点不可分配
4.更新所有父结点,这里父结点分别为512,256等。
以512父结点为例,如果当前1025也已分配则更新512结点标记值为12,否则为10表示部分分配。

下次再分配16k的内存时,会从1025去分配。

这部分只是内存管理逻辑。只是标记了对应内存管理数组的标记值。
真正对外有一个PooledByteBuf,里面保存了对应的chunk物理内存,一个offset,一个length。
之后真正的读写实现操作的是chunk中的物理内存(DirectBuf或堆内存)。

注:内存管理数组下标0不用,从1开始。

四.分配流程代码实例
以页大小为8k,一个chunk为16m,最大层次为11为例。
当前分配大小为9000为例,大于一个页大小。
流程如下:

4.1.分配入口
 long allocate(int normCapacity) {
        if ((normCapacity & subpageOverflowMask) != 0) { // >= pageSize
//大于等于一个页大小,则走这个分支
            return allocateRun(normCapacity);
        } else {
//小于一个页,则从subPage分配内存
            return allocateSubpage(normCapacity);
        }
    }
这里因为normCapacity会被处理成2的幂次方,这里为 16384,即16k。
大于一个页,所以会走方法:allocateRun(normCapacity);

4.2.大于一个页大小的代码
简单来讲,这段代码会从根结点1开始,依次查找出可分配的内存结点。
如果找到则更新当前结点值为12表示不可分配。并更新对应祖先结点,祖先结点要生被更新成部分分配。要么更新成全部分配。
以这个例子为例,则当前可分配结点为1024,父结点为512。因为是第一次分配所以父结点值更新成:9+1=10。表示部分分配。
最终找到1024结点返回给上层。

//d是结点层次,这里层次d为10.
 private int allocateNode(int d) {
        int id = 1;
        int initial = - (1 << d); // has last d bits = 0 and rest all = 1
        byte val = value(id);
        if (val > d) { // unusable
//这里的情况是如果申请的是整个chunk大小,默认16m,这时如果大于d,则说明无内存可用
            return -1;
        }

//依次从根结点开始,直到找到可分配的内存结点,能找到必定val等于层次d
        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);

//当前结点值大于深度d,则查找兄弟结点
            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);
        setValue(id, unusable); // mark as unusable
        updateParentsAlloc(id);
        return id;
    }
展开阅读全文

没有更多推荐了,返回首页