zoukankan      html  css  js  c++  java
  • 10.PoolArena

    PoolArena

    在PooledByteBufAllocate初始化最后一步,就是初始化PoolArena。PoolArena是对内存申请和释放的一个抽象。在类层次上,PoolArena有2个子类DirectArena和HeapArena,并且将内存处理的工作都统一抽象到了PoolArena中。PoolArena主要管理的对象是tinySubpagePools、smallSubpagePools和PoolChunkList,前两者在PoolSubpage一节已有提及,而PoolChunkList则是一组链表对象,并且会根据PoolChunk内存使用率的变化将其中的元素进行转移。

    PoolArena成员介绍

    通过PoolArena的构造函数看一下常用成员变量,这里忽略了metric相关的变量。

    protected PoolArena(PooledByteBufAllocator parent, int pageSize,
              int maxOrder, int pageShifts, int chunkSize, int cacheAlignment) {
            this.parent = parent;
            this.pageSize = pageSize;
            this.maxOrder = maxOrder;
            this.pageShifts = pageShifts;
            this.chunkSize = chunkSize;
            directMemoryCacheAlignment = cacheAlignment;
            directMemoryCacheAlignmentMask = cacheAlignment - 1;
            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, chunkSize);
            q075 = new PoolChunkList<T>(this, q100, 75, 100, chunkSize);
            q050 = new PoolChunkList<T>(this, q075, 50, 100, chunkSize);
            q025 = new PoolChunkList<T>(this, q050, 25, 75, chunkSize);
            q000 = new PoolChunkList<T>(this, q025, 1, 50, chunkSize);
            qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25, chunkSize);
    
            q100.prevList(q075);
            q075.prevList(q050);
            q050.prevList(q025);
            q025.prevList(q000);
            q000.prevList(null);
            qInit.prevList(qInit);
    

    构造函数前半段是一些熟悉的属性——pageSize、maxOrder等,忽略掉这些属性设置外,PoolArena主要的成员变量就是tinySubpagePools和smallSubpagePools,这2个在PoolSubpage一小节已经讲述过,这里看一下PoolChunkList。

    PoolChunkList

    为了理解PoolChunkList,我们先看一下它的构造函数。

    PoolChunkList实例化

    PoolChunkList(PoolArena<T> arena, PoolChunkList<T> nextList, int minUsage, int maxUsage, int chunkSize) {
        this.arena = arena;
        this.nextList = nextList;
        this.minUsage = minUsage;
        this.maxUsage = maxUsage;
        maxCapacity = calculateMaxCapacity(minUsage, chunkSize);
    }
    
    private static int calculateMaxCapacity(int minUsage, int chunkSize) {
        minUsage = Math.max(1, minUsage);
        if (minUsage == 100) {
            return 0;
        }
        return  (int) (chunkSize * (100L - minUsage) / 100L);
    }
    

    在构造函数中定义了PoolChunkList中元素的内存最小、最大使用率,并且根据最小占用率计算出最大内存容量。此外由nextList还可以看出来,PoolChunkList除了自身是一条PoolChunk的链表外,不同的PoolChunkList还会形成一个链表。除了上述几个成员变量外,PoolChunkList还有head和prevList,前者指向PoolChunk的最新加入的节点,后者是PoolChunkList的前继节点。

    PoolChunkList添加PoolChunk

    接着看一下PoolChunkList添加PoolChunk的add方法。add方法在3种情况被调用:

    1. 新创建的Chunk,由一个特殊的PoolChunkList——qInit调用,加入链表中。
    2. 在chunk内存使用率变化时,需要调整其所在链表时调用
    3. 调整链表时,发现当前PoolChunkList不满足内存使用率,递归调用。
    void add(PoolChunk<T> chunk) {
        if (chunk.usage() >= maxUsage) {
            nextList.add(chunk);
            return;
        }
        add0(chunk);
    }
    
    void add0(PoolChunk<T> chunk) {
        chunk.parent = this;
        if (head == null) {
            head = chunk;
            chunk.prev = null;
            chunk.next = null;
        } else {
            chunk.prev = null;
            chunk.next = head;
            head.prev = chunk;
            head = chunk;
        }
    }
    

    add方法首先计算传入的chunk的使用率,如果使用率超过了当前PoolChunkList的最大使用率,则递归调用add方法,直到寻找到合适的PoolChunkList后,调用add0。add0就是流程化的双向链表头插法添加操作。

    PoolChunkList移动PoolChunk

    与add相反的是move操作,在free内存或者调整内存使用率时,move0方法被调用,它会根据chunk的使用率将其从高使用率的PoolChunkList移动到低使用率的PoolChunkList中。

    private boolean move0(PoolChunk<T> chunk) {
        if (prevList == null) {
            assert chunk.usage() == 0;
            return false;
        }
        return prevList.move(chunk);
    }
    
    private boolean move(PoolChunk<T> chunk) {
        if (chunk.usage() < minUsage) {
            return move0(chunk);
        }
        add0(chunk);
        return true;
    }
    

    move0这里首先进行判断,如果当前节点为0,意味着chunk是从q000链表移除,而q000的最小使用率为1%,所以这里做了一个断言chunk使用率为0。之后调用prevList的move方法。
    move方法也是先判断使用率是否小于调用者最小使用率,若小于则再次调用move0方法,否则将chunk添加到调用该方法的PoolChunkList中。

    PoolChunkList申请内存

    boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
        if (normCapacity > maxCapacity) {
            return false;
        }
        for (PoolChunk<T> cur = head; cur != null; cur = cur.next) {
            if (cur.allocate(buf, reqCapacity, normCapacity)) {
                if (cur.usage() >= maxUsage) {
                    remove(cur);
                    nextList.add(cur);
                }
                return true;
            }
        }
        return false;
    }
    

    首先注意到有2个capacity的入参,一个是reqCapacity,表示外部请求申请的内存大小,一个是normCapacity,表示经过规整化为2的幂次方的内存大小,也是实际申请的内存大小,所以在与PoolChunkList的maxCapacity判断时用的maxCapacity。
    满足内存大小申请条件后,用当前PoolChunkList内部的PoolChunk链表一个个尝试申请。如果在某个PoolChunk申请内存后,发现其内存使用率超过了当前PoolChunkList的最大使用率,则将其转移到下一个PoolChunkList中去。

    PoolChunkList初始化

    回到PoolArena中,这里初始化了6个PoolChunkList,组成了一个q000 <-> q025 <-> q050 <-> q075 <-> q100的双向链表。qInit比较特殊,他的后继节点是q000,但前继节点是自身。从名字上也可以看出,qInit负责创建PoolChunk,且在PoolChunk使用率超过qInit的maxUsage(25%)后,将其转移到q000链表去。因为q000最小使用率就是1%,再小就没必要留着PoolChunk了,因此q000的前继节点为null。

    PoolArena申请内存

    外界通过调用allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity)方法来申请内存。

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

    这个方法首先调用了一个抽象方法newByteBuf(int maxCapacity)获取到PooledByteBuf,然后将申请的内存通过的allocate方法放入这个ByteBuf中。

    private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
    final int normCapacity = normalizeCapacity(reqCapacity);
    if (isTinyOrSmall(normCapacity)) {
        int tableIdx;
        PoolSubpage<T>[] table;
        boolean tiny = isTiny(normCapacity);
        if (tiny) {
            if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
                return;
            }
            tableIdx = tinyIdx(normCapacity);
            table = tinySubpagePools;
        } else {
            if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
                return;
            }
            tableIdx = smallIdx(normCapacity);
            table = smallSubpagePools;
        }
        final PoolSubpage<T> head = table[tableIdx];
    
        synchronized (head) {
            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, null, handle, reqCapacity);
                incTinySmallAllocation(tiny);
                return;
            }
        }
            synchronized (this) {
                allocateNormal(buf, reqCapacity, normCapacity);
            }
            incTinySmallAllocation(tiny);
            return;
        }
        if (normCapacity <= chunkSize) {
            if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
                return;
            }
            synchronized (this) {
                allocateNormal(buf, reqCapacity, normCapacity);
                ++allocationsNormal;
            }
        } else {
            allocateHuge(buf, reqCapacity);
        }
    }
    

    可以看出这个方法是相当的长,但总体逻辑还算清晰,惯例先分步骤:

    1. 将申请内存规整化。
    2. 申请tiny、small级别的内存。
    3. 申请normal级别的内存。
    4. 申请huge级别的内存。

    将申请内存规整化。

    将内存大小规整化这个概念前面多次提到,他的作用是将内存大小向上对齐。对于512B以下大小的内存,对齐到大于申请内存的下一个16B的倍数。比如15B,会对齐到16B,41B会对齐到48B。对于大于512B的内存,对齐到大于申请内存的下一个2的幂次方,比如申请600B,会对齐到1KB,申请3KB,会对齐到4KB。
    它的实现大量使用位运算,代码如下。由于directMemoryCacheAlignment默认为0,且通常也不会设置,这里忽略了。

    int normalizeCapacity(int reqCapacity) {
        checkPositiveOrZero(reqCapacity, "reqCapacity");
        if (reqCapacity >= chunkSize) {
            return reqCapacity;
        }
        if (!isTiny(reqCapacity)) {
            int normalizedCapacity = reqCapacity;
            normalizedCapacity --;
            normalizedCapacity |= normalizedCapacity >>>  1;
            normalizedCapacity |= normalizedCapacity >>>  2;
            normalizedCapacity |= normalizedCapacity >>>  4;
            normalizedCapacity |= normalizedCapacity >>>  8;
            normalizedCapacity |= normalizedCapacity >>> 16;
            normalizedCapacity ++;
            if (normalizedCapacity < 0) {
                normalizedCapacity >>>= 1;
            }
            return normalizedCapacity;
        }
    
        if ((reqCapacity & 15) == 0) {
            return reqCapacity;
        }
        return (reqCapacity & ~15) + 16;
    }
    

    首先检验了内存是否不是负数,否则直接抛出异常。之后判断申请内存是否超出了ChunkSize级别,即16MB,若超出这个大小,则达到了huge级别,无需规整化。
    若不是tiny级别内存,则先自减,分别与自身无符号右移1、2、4、8、16位做或运算,再自增。完成后还做了是否越界的判断,越界时,无符号右移一位。
    比如600B,二进制为0010 0101 1000,自减后变成0010 0101 0111,做完无符号右移后,变成0011 1111 1111,自增后,变成0100 0000 0000,即2^10=1024B。
    若为tiny级别,&15==0表明刚好是16的倍数,直接返回。否则对15取反后,与内存做与运算,再加上16。因为15取反后,低4位为0,高位全部为1,相当于掩码。

    申请tiny、small级别的内存

    在对内存规整化以后,首先需要判断它在哪个范围。对tiny、small级别来说采用的是 (subpageOverflowMask & normCapacity) == 0 这样一个判断条件,其中subpageOverflowMask = ~(pageSize - 1),默认低13位为0,其余高位为1。之后又使用 (normCapacity & 0xFFFFFE00) == 0 进一步划分tiny和small。tiny、small,包括后续的norm,都会先尝试用缓存分配,如果分配成功则直接返回。缓存分配留待后续。
    在缓存分配失败后,会去相应的PoolArena中的PoolSubpage数组定位到对应大小PoolSubpage的head结点。
    在分配之前,先对head加锁,因为此时PoolChunk.allocateSubpage和PoolChunk.free可能会并发修改PoolSubpage链表。
    加锁完成进入临界区,迭代head的下一个非空PoolSubpage节点,调用其allocate方法进行内存分配。
    在分配完成后,还需要调用其所在PoolChunk的initBufWithSubpage方法,最终会调用ByteBuf.init方法进行初始化。分配完成后,增加对应的分配计数器
    若不存在非空的PoolSubpage节点,则还是需要在normal分配。

    申请normal级别的内存

    在分配normal级别的内存时,需要对PoolArena对象加锁,防止其他线程同时分配内存。具体分配的代码如下

    private 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)) {
            return;
        }
        // Add a new chunk.
        PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
        boolean success = c.allocate(buf, reqCapacity, normCapacity);
        assert success;
        qInit.add(c);
    }
    

    首先尝试在5个PoolChunkList中分配内存。PoolChunkList分配内存的相关代码在前文已经涉及。它最终会将分配的PoolChunk放在合适使用率的PoolChunkList中。
    若PoolChunkList中没有分配成功(比如在初始状态下,PoolChunkList中除了head节点没有其他PoolChunk对象),在会新增一个PoolChunk,调用新增PoolChunk分配内存。分配完成并且初始化后,将PoolChunk加入qInit链表中,并移动到符合使用率的链表中。
    分配成功后,会在PoolArena中将对应计数器自增。

    申请huge级别内存

    由于huge级别的内存过大,不适合池化管理,所以申请的是unpooled的内存。申请完huge内存后,依然进行初始化和计数。

    private void allocateHuge(PooledByteBuf<T> buf, int reqCapacity) {
        PoolChunk<T> chunk = newUnpooledChunk(reqCapacity);
        activeBytesHuge.add(chunk.chunkSize());
        buf.initUnpooled(chunk, reqCapacity);
        allocationsHuge.increment();
    }
    
  • 相关阅读:
    sql server 中各个系统表的作用==== (转载)
    后台动态设置前台标签内容和属性
    利用C#编写一个简单的抓网页应用程序
    如何创建和使用Web Service代理类
    jdbc如何取得存储过程return返回值
    子窗口和父窗口的函数或对象能否相互访问 (转载)
    把aspx文件编译成DLL文件
    C#中的类型转换
    c#中对文件的操作小结
    转贴一篇 自定义数据库 希望对你有帮助
  • 原文地址:https://www.cnblogs.com/spiritsx/p/12275509.html
Copyright © 2011-2022 走看看