美文网首页Nettyhello worldnetty
自顶向下深入分析Netty(十)--PoolChunkList

自顶向下深入分析Netty(十)--PoolChunkList

作者: Hypercube | 来源:发表于2017-07-28 21:23 被阅读190次

    JEMalloc分配算法文中介绍过,Chunk块随着内存使用率的变化,有六种状态:QINIT,Q0,Q25,Q50,Q75,Q100。可知,一种状态可能有多个Chunk块,Netty使用PoolChunkList来存储这些Chunk块,它们之间的关系如下图所示:

    Chunk与PoolChunkList

    有六种状态所以有六个PoolChunkList,它们之间除了QINIT外形成双向链表;PoolChunkList中的Chunk块也形成双向链表,其中头结点是双向链表的尾部,且新加入的节点也加到尾部。以Q25依次加入Chunk1,Chunk2,Chunk3为例,形成的链表如图,其中Head节点是最后加入的Chunk3节点。Chunk随着内存使用率的变化,会在PoolChunkList中移动,初始时都在QINI,随着使用率增大,移动到Q0,Q25等;随着使用率降低,又移回Q0,当Q0中的Chunk块不再使用时,从Q0中移除。Netty对各状态内存使用率的定义稍有不同,见下表:

    状态 最小内存使用率 最大内存使用率
    QINIT 1 25
    Q0 1 50
    Q25 25 75
    Q50 50 100
    Q75 75 100
    Q100 100 100

    明白了这些,再来分析源码实现。首先看成员变量:

        private final PoolArena<T> arena; // 所属的Arena
        private final int minUsage; // 状态的最小内存使用率
        private final int maxUsage; // 状态的最大内存使用率
        private final int maxCapacity; // 该状态下的一个Chunk可分配的最大字节数
    
        private PoolChunk<T> head; // head节点
        private final PoolChunkList<T> nextList; // 下一个状态
        private PoolChunkList<T> prevList; // 上一个状态
    

    构造方法如下:

        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;
            // 计算该状态下,一个Chunk块可以分配的最大内存
            maxCapacity = calculateMaxCapacity(minUsage, chunkSize);
        }
        
         private static int calculateMaxCapacity(int minUsage, int chunkSize) {
            minUsage = minUsage0(minUsage);
    
            if (minUsage == 100) {
                return 0;   // Q100 不能再分配
            }
            // Q25中一个Chunk可以分配的最大内存为0.75 * ChunkSize
            return  (int) (chunkSize * (100L - minUsage) / 100L);
        }
    

    形成状态PoolChunkList的双向链表代码在PoolArena中,再次列出如下:

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

    其中的prevList()方法如下:

        void prevList(PoolChunkList<T> prevList) {
            assert this.prevList == null; // 这个方法只应该在创建时调用一次
            this.prevList = prevList;
        }
    

    接着分析,在PoolChunkList中的PoolChunk形成的双向链表的操作,代码如下:

        // 增加一个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;
            }
        }
    
        // 删除一个Chunk节点
        private void remove(PoolChunk<T> cur) {
            if (cur == head) {
                head = cur.next;
                if (head != null) {
                    head.prev = null;
                }
            } else {
                PoolChunk<T> next = cur.next;
                cur.prev.next = next;
                if (next != null) {
                    next.prev = cur.prev;
                }
            }
        }
    

    将一个PoolChunk加入到PoolChunkList中的代码如下:

        void add(PoolChunk<T> chunk) {
            if (chunk.usage() >= maxUsage) {
                nextList.add(chunk);
                return;
            }
            add0(chunk);
        }
    

    注意该方法实质是一个递归调用,在if语句中会找到真正符合状态的PoolChunkList,然后才执行add0()加入PoolChunk节点。随着内存使用率的增加,需要调用add()方法将PoolChunk向右移动到正确状态的PoolChunkList;同理,随着内存使用率的减小,也需要一个方法将PoolChunk向左移动到正确状态。在实现中,这个方法为move(),名字带有歧义,忽略名字,代码如下:

        private boolean move(PoolChunk<T> chunk) {
            assert chunk.usage() < maxUsage;
    
            if (chunk.usage() < minUsage) {
                return move0(chunk); // 向左移动到正确状态,递归调用
            }
    
            add0(chunk); // 到达正确状态后,加入双向链表
            return true;
        }
    
        private boolean move0(PoolChunk<T> chunk) {
            if (prevList == null) {
                // 此时表示chunk为Q0状态,且还需要移动,说明Chunk使用率为0
                assert chunk.usage() == 0;
                return false;
            }
            return prevList.move(chunk); // 向左移动
        }
    

    接下来,分析关键的分配过程,代码如下:

        boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
            // 该状态下还没有符合的Chunk块
            // 申请的内存已超过一个Chunk块可以分配的最大内存
            if (head == null || normCapacity > maxCapacity) {
                return false;
            }
    
            // Chunk链表中寻找满足需求的Chunk块
            for (PoolChunk<T> cur = head;;) {
                long handle = cur.allocate(normCapacity);
                if (handle < 0) {
                    cur = cur.next;
                    if (cur == null) {
                        return false; // 没有满足需求
                    }
                } else {
                    // 满足需求,在该Chunk块中分配
                    cur.initBuf(buf, handle, reqCapacity);
                    if (cur.usage() >= maxUsage) {
                        remove(cur);    
                        nextList.add(cur); // 分配后需要向右移动至符合的状态
                    }
                    return true;
                }
            }
        }
    

    分配过程简单明了,释放过程也如此,代码如下:

        boolean free(PoolChunk<T> chunk, long handle) {
            chunk.free(handle); // chunk释放占用的内存
            if (chunk.usage() < minUsage) {
                remove(chunk);
                return move0(chunk); // 向左移动到符合的状态
            }
            return true;
        }
    

    最后,销毁PoolChunkList的方法如下:

        void destroy(PoolArena<T> arena) {
            PoolChunk<T> chunk = head;
            while (chunk != null) {
                arena.destroyChunk(chunk);  // 释放Chunk
                chunk = chunk.next;
            }
            head = null; // GC回收节点
        }
    

    依次将Chunk中的内存销毁,然后由GC回收链表节点。至此,PoolChunkList分析完毕。
    相关链接:

    1. JEMalloc分配算法
    2. PoolArena
    3. PoolChunk
    4. PoolSubpage
    5. PooThreadCache

    相关文章

      网友评论

        本文标题:自顶向下深入分析Netty(十)--PoolChunkList

        本文链接:https://www.haomeiwen.com/subject/debflxtx.html