Netty源码分析:PoolChunkList

Posted HelloWorld_EE

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty源码分析:PoolChunkList相关的知识,希望对你有一定的参考价值。

Netty源码分析:PoolChunkList

在博文 Netty源码分析:PoolArena中我们知道,在第一次申请内存时,会调用如下的allocateNormal方法来新建一个Chunk,然后在此Chunk上分配内存。分配完成之后会将这个Chunk添加到名为qInit的PoolChunkList中。在下次分配内存时会先尝试在6个PoolChunkList来寻找chunk来分配内存。

    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;
        

        // Add a new chunk.
        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这个类。

1、属性和构造函数

在PoolArean这个类实例化6个PoolChunkList的代码如下:

        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;

借此机会就先从PoolChunkList这个类的构造函数开始介绍。PoolChunkList这个类的字段和构造函数如下:

    final class PoolChunkList<T> 
        private final PoolArena<T> arena; //表示这个chunkList所那个所持有
        //构成链表
        private final PoolChunkList<T> nextList;
        PoolChunkList<T> prevList;

        private final int minUsage;//最小使用量
        private final int maxUsage;//最大使用量

        private PoolChunk<T> head;//保存的是由chunk构成的链表的头节点。

        // TODO: Test if adding padding helps under contention
        //private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;

        PoolChunkList(PoolArena<T> arena, PoolChunkList<T> nextList, int minUsage, int maxUsage) 
            this.arena = arena;
            this.nextList = nextList;
            this.minUsage = minUsage;
            this.maxUsage = maxUsage;
        

字段和构造都比较好理解哈,从中知道如下两点即可:

1)每个PoolChunkList中用head字段维护一个PoolChunk链表的头部

2)PoolChunkList自己也将构成一个链表,例如:在PoolArena实例中就存在如下的6个PoolChunkList,且这6个PoolChunkList构成的链表为:qInit->q000<->q025<->q050<->q075<->q100,且qInit.prevList = qInit,具体如下图所示。

qInit:存储已使用内存在0-25%的chunk,即保存剩余内存在75%~100%的chunk
q000:存储已使用内存在1-50%的chunk
q025:存储已使用内存在25-75%的chunk
q050:存储已使用内存在50-100%个chunk
q075:存储已使用内存在75-100%个chunk
q100:存储已使用内存在100%chunk

为什么链表是这样的顺序排列的?

原因在于:随着chunk中page的不断分配和释放,会导致很多碎片内存段,大大增加了之后分配一段连续内存的失败率,针对这种情况,可以把内存使用量较大的chunk放到PoolChunkList链表更后面,这样就便于内存的成功分配。

从PoolArena类中如下的allocateNormal中也可以反应出这一点:首先是在q050这个内存使用量在[50,100]的PoolChunkList中来尝试分配,如果分配失败,可能原因是需要分配的内存过大,则然后就会依次尝试在内存使用量较小的PoolChunkList来完成内存分配。

    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;
        

        // Add a new chunk.
        PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
        long handle = c.allocate(normCapacity);
        assert handle > 0;
        c.initBuf(buf, handle, reqCapacity);
        qInit.add(c);
    

2、add(PoolChunk chunk)

下面来看这个add函数,具体代码如下:

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

        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节点在这个PoolChunkList的Chunk链表的头部。

注意:如果这个chunk的内存使用量大于该PoolChunkList的最大使用量,则添加到该PoolChunkList的下一个PoolChunkList中去。这里就保证了每个PoolChunkList中都是存储的是其[minUsage,maxUsage]范围内的chunk。

3、allocate

下面来看下怎么来利用PoolChunkList来进行内存分配的。

    boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) 
        if (head == null)  //根据head是否为null来判断此PoolChunkList没有chunk可用,如果没有,则返回false。
            return false;
        

        for (PoolChunk<T> cur = head;;) 
        //利用chunk来进行内存分配,具体逻辑可以见相应的博文
            long handle = cur.allocate(normCapacity);
            if (handle < 0) 
                cur = cur.next;
                if (cur == null) 
                    return false;
                
             else 
                cur.initBuf(buf, handle, reqCapacity);
                if (cur.usage() >= maxUsage) 
                    remove(cur);
                    nextList.add(cur);
                
                return true;
            
        
    

该函数也比较好理解哈,首先检查head是否为null,如果为null则说明此PoolChunkList没有chunk可用,如果不为null则从head节点开始分配内存并初始化到buf上。

要注意的一点是:在分配内存之后,会对该chunk的使用量进行判断,如果使用量大于maxUsage,则将此chunk从该PoolChunkList中移除然后将此chunk添加到后一个PoolChunkList中。

当然,chunk除了内存被分配,也会被释放,被释放之后,使用量减少,即内存剩余量增大,如果该chunk的使用量小于该PoolChunkList的minUsage,则就可能会被添加到前一个PoolChunkList中。

从下面的代码中注意一点特殊情况:如果没有下一个PoolChunkList,则说明该chunk的使用量为0,Arena就将此chunk销毁。

    void free(PoolChunk<T> chunk, long handle) 
        chunk.free(handle);
        if (chunk.usage() < minUsage) 
            remove(chunk);
            if (prevList == null) 
                assert chunk.usage() == 0;
                arena.destroyChunk(chunk);
             else 
                prevList.add(chunk);
            
        
     

就是因为存在内存的分配和释放,而会导致chunk在不同的PoolChunkList中移动,但是为了防止移动的太过于频繁,则采取的措施是:每个chunkList的都有一个上下限:minUsage和maxUsage,两个相邻的chunkList,前一个的maxUsage和后一个的minUsage必须有一段交叉值进行缓冲。以下为PoolArena中包含的6个PoolChunkList的minUsage和maxUsage。

qInit:存储已使用内存在0-25%的chunk,即保存剩余内存在75%~100%的chunk
q000:存储已使用内存在1-50%的chunk
q025:存储已使用内存在25-75%的chunk
q050:存储已使用内存在50-100%个chunk
q075:存储已使用内存在75-100%个chunk
q100:存储已使用内存在100%chunk

小结

比较简单哈。看完我们需要记住如下的两点:

1、 每个PoolChunkList中用head字段维护一个PoolChunk链表的头部

2、当需要进行内存分配时,会依次遍历该PoolChunkList中的PoolChunk节点来完成,完成之后,会判断此PoolChunk的内存使用量是否大于该PoolChunkLis他的maxUsage,如果大于,则将此chunk放到下一个PoolChunkList中。

3、当chunk由于内存释放的原因而导致内存使用量减少,即剩余内存量增大,如果小于此PoolChunkList的minUsage,则将其加入到上一个PoolChunkList中去。

以上是关于Netty源码分析:PoolChunkList的主要内容,如果未能解决你的问题,请参考以下文章

netty里的ByteBuf扩容源码分析

Netty源码分析(七) PoolChunk

源码分析Netty4专栏

源码分析Netty4专栏

Netty-源码分析LineBasedFrameDecoder

Netty源码分析:read