Netty源码分析:PoolArena

Posted HelloWorld_EE

tags:

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

Netty源码分析:PoolArena

Arena本身是指一块区域,在内存管理中,Memory Arena是指内存中的一大块连续的区域,PoolArena就是Netty的内存池实现类。

Netty的PoolArena是由多个Chunk组成的大块内存区域,而每个Chunk则由一个或者多个Page组成(在博文Netty源码分析:PoolChunk已经明确了这点),因此,对内存的组织和管理也就主要集中在如何管理和组织Chunk和Page了。

先说结论:PoolArena通过6个PoolChunkList来管理PoolChunk,而每个PoolChunk由N个PoolSubpage构成,即将PoolChunk的里面底层实现 T memory分成N段,每段就是一个PoolSubpage。当用户申请一个Buf时,使用Arena所拥有的chunk所管辖的page分配内存,内存分配的落地点为 T memory上。

看一个例子

假设每个Page的大小为8192。在下面的代码中就是申请两块buf。所得到的结果就是:byteBuf将利用PoolChunk的第0个PoolSubpage进行分配,分配的buf的内存为:从memory[0]开始且长度为15最大长度为16;byteBuf1将利用PoolChunk的第1个PoolSubpage进行分配,分配的落地点从memory[8192]开始且长度为17最大长度为32的内存.

截图如下:

    public class TestByteBuf 

        public static void main(String[] args) 
            //1 分配一个ByteBuf,然后写入数据到此Buf

            PooledByteBufAllocator allocator = new PooledByteBufAllocator(false);
            ByteBuf byteBuf = allocator.heapBuffer(15);

            ByteBuf byteBuf1 = allocator.heapBuffer(17);

            String content = "wojiushimogui";
            byteBuf.writeBytes(content.getBytes());
            System.out.println("1、readerIndex:"+byteBuf.readerIndex());
            System.out.println("1、writerIndex:"+byteBuf.writerIndex());
        
    

本文将从源码的角度主要介绍下PoolArena。

1、属性

    abstract class PoolArena<T> 

        static final int numTinySubpagePools = 512 >>> 4;

        final PooledByteBufAllocator parent;//表示该PoolArena的allocator

        private final int maxOrder;//表示chunk中由Page节点构成的二叉树的最大高度。默认11
        final int pageSize;//page的大小,默认8K
        final int pageShifts;//pageShifts=log(pageSize),默认13
        final int chunkSize;//chunk的大小
        final int subpageOverflowMask;//该变量用于判断申请的内存大小与page之间的关系,是大于,还是小于

        final int numSmallSubpagePools;//用来分配small内存的数组长度
        /*
        tinySubpagePools来缓存(或说是存储)用来分配tiny(小于512)内存的Page;
        smallSubpagePools来缓存用来分配small(大于等于512且小于pageSize)内存的Page
        */
        private final PoolSubpage<T>[] tinySubpagePools;
        private final PoolSubpage<T>[] smallSubpagePools;
        //用来存储用来分配给Normal(超过一页)大小内存的PoolChunk。
        private final PoolChunkList<T> q050;
        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;

PoolArena属性的各个含义已在代码中进行了解释哈。下面来看构造函数。

2、构造函数

构造函数的代码如下:

        protected PoolArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) 
        //从PooledByteBufAllocator中传送过来的相关字段值。
            this.parent = parent;
            this.pageSize = pageSize;
            this.maxOrder = maxOrder;
            this.pageShifts = pageShifts;
            this.chunkSize = chunkSize;
            subpageOverflowMask = ~(pageSize - 1);//该变量用于判断申请的内存大小与page之间的关系,是大于,还是小于
            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;
         

该构造函数主要干了如下几件事

1)初始化parent、pageSize、maxOrder、pageShifts等字段

2)实例化了如下两个数组,这两个数组相当重要,稍后将进行详细的介绍。

    private final PoolSubpage<T>[] tinySubpagePools;
    private final PoolSubpage<T>[] smallSubpagePools; 

3)创建了6个Chunk列表(PoolChunkList)来缓存用来分配给Normal(超过一页)大小内存的PoolChunk,每个PoolChunkList中用head字段维护一个PoolChunk链表的头部,每个PoolChunk中有prev,next字段。而PoolChunkList内部维护者一个PoolChunk链表头部。
这6个PoolChunkList解释如下:
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
这六个PoolChunkList也通过链表串联,串联关系是:qInit->q000<->q025<->q050<->q075<->q100,且qInit.prevList = qInit,图示如下:

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

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

3、tinySubpagePools和smallSubpagePools

下面主要想介绍下 如下两个数组

    private final PoolSubpage<T>[] tinySubpagePools;
    private final PoolSubpage<T>[] smallSubpagePools;

这两个数组比较重要,tinySubpagePools来缓存(或说是存储)用来分配tiny(小于512)内存的Page;smallSubpagePools来缓存用来分配small(大于等于512且小于pageSize)内存的Page。

3.1 PoolSubpage[] tinySubpagePools

PoolSubpage[] tinySubpagePools,数组默认长度为32(512 >>4),里面存储的元素是专门用来分配小内存tiny(小于512)。

tinySubpagePools数组中的每个元素都是一个Page链表。而构造函数中的这行代码tinySubpagePools[i] = newSubpagePoolHead(pageSize);就是通过调用newSubpagePoolHead(pageSize)来对tinySubpagePools数组中的元素进行实例化,此时链表中只有一个Page节点且前后指针都指向自己,注意:通过new PoolSubpage(pageSize)实例化的page实例还不能真正用于内存分配,还需要在用之前调用page.init(elemSize)方法进行初始化,但是每个tinySubpagePools[i]的head只是起着链表头标识的作用,并不真正的作为内存分配的chunk

    private PoolSubpage<T> newSubpagePoolHead(int pageSize) 
        PoolSubpage<T> head = new PoolSubpage<T>(pageSize);
        head.prev = head;
        head.next = head;
        return head;
    

在博文 Netty源码分析:PoolSubpage中我们已经了解到:当new一个Page实例之后,会通过如下的page.addToPool()方法将此Page实例加入到tinySubpagePools或smallSubpagePools数组中。

    PoolSubpage.java
    private void addToPool() 
        PoolSubpage<T> head = chunk.arena.findSubpagePoolHead(elemSize);
        assert prev == null && next == null;
        prev = head;
        next = head.next;
        next.prev = this;
        head.next = this;
     

加入的位置是通过调用如下的arena.findSubpagePoolHead(elemSize)来完成,如下:

    PoolArena.java
    PoolSubpage<T> findSubpagePoolHead(int elemSize) 
        int tableIdx;
        PoolSubpage<T>[] table;
        if (isTiny(elemSize))  // < 512
            tableIdx = elemSize >>> 4;//等于与elemSize除以16
            table = tinySubpagePools;
         else 
            tableIdx = 0;
            elemSize >>>= 10;
            while (elemSize != 0) 
                elemSize >>>= 1;
                tableIdx ++;
            
            table = smallSubpagePools;
        

        return table[tableIdx];
    

从函数findSubpagePoolHead中可以得到:

a)当elemSize小于512时,块大小为elemSize的page将存储在tinySubpagePools[elemSize>>>4]的位置上,用于之后分配大小为elemSize(小于512的tiny内存)的内存请求。也就是说:tinySubpagePools[tableIdx]处的page负责分配大小为(16*tableIdx,0<tableIdx<=31)内存块,即tinySubpagePools[1]处的page负责分配大小为16的块内存,tinySubpagePools[2]处的page负责分配大小为32的块内存,按这种以 16 步进的方式类推

b)当elemSize在区间[512,pageSize)范围内时,块大小为elemSize的page将存储在tinySubpagePools[(log(elemSize)-10)+1]的位置上,用于之后分配大小为elemSize(大于等于512小于pageSize的small内存)的内存请求。也就是说:smallSubpagePools[0]处的page负责分配大小为512的块内存,smallSubpagePools[1]处的page负责分配大小为1024的内存,smallSubpagePools[2]处的page负责分配大小为2918的内存,按这种倍增的方式依次类推

总结一下:首先PoolSubpage里面可以再次分成大小相等的内存空间,一个PoolSubpage的内存大小默认为8K,当一个线程需要申请16个字节时,如果存在一个PoolSubpage里面每一小块的空间为16个字节,并且未被全部占有,则直接在此Poolsubpage中分配;如果不存在块大小为16个字节的Poolsubpage,则首先分配一个PoolSubpage,然后将初始化为每个块容量为16字节,然后将该PoolSubpage放入到PoolArena的tinySubpagePools数组的下标为1的地方,如果该地方已经有元素了,则tinySubpagePools[1]会维护一条链。

3.2 PoolSubpage[] smallSubpagePools

上面详细介绍了tinySubpagePools,继续看PoolSubpage[] smallSubpagePools。

其存储思维与tinySubpagePools一样。如下:

首先PoolSubpage里面可以再次分成大小相等的内存空间,一个PoolSubpage的内存大小默认为8K,当一个线程需要申请1024个字节时,如果存在一个PoolSubpage里面每一小块的空间为1024个字节,并且未被全部占有,则直接在此Poolsubpage中分配;如果不存在块大小为1024个字节的Poolsubpage,则首先分配一个PoolSubpage,然后将初始化为每个块容量为1024字节,然后将该PoolSubpage放入到PoolArena的smallSubpagePools数组的下标为1的地方,如果该地方已经有元素了,则smallSubpagePools[1]会维护一条链。

上面我们知道Netty把小于512的内存叫做tiny,并以16步进的方式来组织,因此tinySubpagePools数组的长度为:512/16=32.

那么怎么计算smallSubpagePools的数组长度呢?

首先,Netty把大于等于512小于pageSize(8192)的内存空间看成为small。small内存是翻倍来组织,也就是会产生512、1024、2048等。在如上的构造函数中中我们看到numSmallSubpagePools = pageShifts - 9;,为什么是利用pageShifts减9来得到numSmallSubpagePools,原因在于:由于small的区间是从512开始进行的翻倍,其实我们只要先求出 pageShifts = log(pageSize) ,然后减去9(512是2的9次幂),故默认smallSubpagePools数组长度为4。

3.3 总结

1、如下两个数组特别关键,PoolArena类中的如下两个数组一个是缓存用来分配tiny(小于512)内存的Page,一个是缓存用来分配small(大于等于512小于pageSize)内存的Page。

    private final PoolSubpage<T>[] tinySubpagePools;
    private final PoolSubpage<T>[] smallSubpagePools;

2、还有6个PoolChunkList用来缓存用来分配Normal(大于pageSize)内存的Chunk。

4 内存分配:allocate

下面来看下PoolArena是如何借助于Chunk、Page来实现内存分配的

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

4.1 newByteBuf

PoolArena没有对newByteBuf进行实现,这是因为它不知道子类是基于何种实现。其子类HeapArena对此方法的实现代码如下:

    HeapArena.java

        @Override
        protected PooledByteBuf<byte[]> newByteBuf(int maxCapacity) 
            return PooledHeapByteBuf.newInstance(maxCapacity);//分析
        

该函数的功能为:实例化一个 PooledHeapByteBuf 对象。

    PooledHeapByteBuf.java        
    static PooledHeapByteBuf newInstance(int maxCapacity) 
        PooledHeapByteBuf buf = RECYCLER.get();//分析
        buf.setRefCnt(1);
        buf.maxCapacity(maxCapacity);
        return buf;
     

其中RECYCLER定义如下:

    private static final Recycler<PooledHeapByteBuf> RECYCLER = new Recycler<PooledHeapByteBuf>() 
        @Override
        protected PooledHeapByteBuf newObject(Handle handle) 
            return new PooledHeapByteBuf(handle, 0);
        
    ;    

Recycler.java

    @SuppressWarnings("unchecked")
    public final T get() 
        Stack<T> stack = threadLocal.get();
        DefaultHandle handle = stack.pop();
        if (handle == null) 
            handle = stack.newHandle();
            handle.value = newObject(handle);
        
        return (T) handle.value;
      

该函数的功能:得到一个PooledHeapByteBuf实例。

到这里之后,我就很困惑,这里不是直接得到了一个 PooledHeapByteBuf 类型的buf了么,在以前的实践中,我们当得到一个ByteBuffer后,就直接分配好内存了哈然后就直接可以用了,这里为什么不是这样,为什么还要调用后续的allocate方法进行分配了?

跟踪代码后发现:确实不一样, PooledHeapByteBuf 目前还只是一个空壳。我们还需要确定这个PooledHeapByteBuf在Chunk的底层memory所处在的位置(即设置offset)。memory才是我们最终落脚的地方。 如下为PooledByteBuf所涉及的字段。

    abstract class PooledByteBuf<T> extends AbstractReferenceCountedByteBuf 

        private final Recycler.Handle recyclerHandle;

        protected PoolChunk<T> chunk;
        protected long handle;
        protected T memory;
        protected int offset;
        protected int length;
        int maxLength;

        private ByteBuffer tmpNioBuf;

4.2 allocate

看allocate这个方法

    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
                //先尝试在poolThreadCache中分配,如果分配成功,则返回,否则借用tinySubpagePools中缓存的Page来进行内存分配
                if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) 
                    // was able to allocate out of the cache so move on
                    return;
                
                tableIdx = tinyIdx(normCapacity);
                table = tinySubpagePools;
             else 
            //先尝试在poolThreadCache中分配,如果分配成功,则返回,否则借用smallSubpagePools中缓存的Page来进行内存分配
                if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) 
                    // was able to allocate out of the cache so move on
                    return;
                
                tableIdx = smallIdx(normCapacity);
                table = smallSubpagePools;
            
                //走到这里,说明尝试在poolThreadCache中分配失败,开始尝试借用tinySubpagePools或smallSubpagePools缓存中的Page来进行分配
            synchronized (this) 
                final PoolSubpage<T> head = table[tableIdx];
                final PoolSubpage<T> s = head.next;
                if (s != head) //第一次在此位置申请内存的时候,s==head,则会调用allocateNormal方法来分配。
                    assert s.doNotDestroy && s.elemSize == normCapacity;
                    long handle = s.allocate();
                    assert handle >= 0;
                    s.chunk.initBufWithSubpage(buf, handle, reqCapacity);
                    return;
                
            
         else if (normCapacity <= chunkSize) 
            if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) 
                // was able to allocate out of the cache so move on
                return;
            
         else 
            // Huge allocations are never served via the cache so just call allocateHuge
            allocateHuge(buf, reqCapacity);
            return;
        
        /*
        有如下两种情况会调用此方法来进行内存分配:
        1)分配一个page以上的内存
        2)对于小于pageSize的内存,如果是第一次申请而因为没有tinySubpagePools或smallSubpagePools没有何时的subpage,则也会调用此方法
        */
        allocateNormal(buf, reqCapacity, normCapacity);
     

该方法的逻辑如下:

1、默认先尝试从poolThreadCache中分配内存,PoolThreadCache利用ThreadLocal的特性,消除了多线程竞争,提高内存分配效率;首次分配时,poolThreadCache中并没有可用内存进行分配,当上一次分配的内存使用完并释放时,会将其加入到poolThreadCache中,提供该线程下次申请时使用。

2、如果在poolThreadCache中分配内存没有成功,则对于分配小内存,先尝试从tinySubpagePools或smallSubpagePools中分配内存;如果没有合适subpage,则采用方法allocateNormal分配内存。

3、如果分配一个page以上的内存,直接采用方法allocateNormal分配内存。

无论是在poolThreadCache中分配内存,还是尝试在tinySubpagePools、smallSubpagePools以及采用方法allocateNormal分配内存,首先需要解决的问题是:调用 normalizeCapacity方法来扩张请求内存的大小。

normalizeCapacity方法代码如下,下面具体来看

    int normalizeCapacity(int reqCapacity) 
        if (reqCapacity < 0) 
            throw new IllegalArgumentException("capacity: " + reqCapacity + " (expected: 0+)");
        
        if (reqCapacity >= chunkSize) 
            return reqCapacity;
        

        if (!isTiny(reqCapacity))  // >= 512
            // Doubled

            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;
        

        // Quantum-spaced
        if ((reqCapacity & 15) == 0) 
            return reqCapacity;
        

        return (reqCapacity & ~15) + 16;
     

这个normlizeCapacity方法的作用为:对请求的内存进行扩展,但是扩张是根据请求的内存大小有不同的策略,具体如下:

1)如果请求的内存大小超过了chunkSize,则说明无法分配,则直接返回。

2)如果请求的内存大小在[512,chunkSize]区间,则返回一个比其稍大的2的幂次方数作为最终的内存大小。例如:请求大小在(512,1024]区间,返回1024,当在(1024,2048]区间,返回2048

3)如果请求的内存大小小于512,则返回一个比其稍大的16最小倍数作为最终的内存大小。例如:(16,32]区间返回32,(32,48]区间返回48。

总结:分配的内存大小小于512时内存池分配tiny块,大小在[512,pageSize]区间时分配small块,tiny块和small块基于page分配,分配的大小在(pageSize,chunkSize]区间时分配normal块,normall块基于chunk分配,内存大小超过chunk,内存池无法分配这种大内存,直接由JVM堆分配,内存池也不会缓存这种内存。

2 根据请求内存的大小内存池会对应的几种分配内存的策略,因此在代码中走的是不同的分支。
1)小于512的tiny块和在区间[512,pageSize]大小的small块是基于page分配的。2)大小在(pageSize,chunkSize]的normal块是在chunk分配的。3)大于chunkSize的内存是直接由JVM分配的。

4.3 allocateNormal方法

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

1)当第一次进行内存分配时,6个chunkList都没有chunk可以分配内存,需通过方法newChunk新建一个chunk实例进行之后的内存分配,并通过代码qInit.add(c)添加到qInit列表中。

    HeapArena
    @Override
    protected PoolChunk<byte[]> newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize) 
        return new PoolChunk<byte[]>(this, new byte[chunkSize], pageSize, maxOrder, pageShifts, chunkSize);
    

newChunk方法直接new了一个PoolChunk实例,在博文Netty源码分析:PoolChunk详细介绍了此构造函数,如果想了解,可以看看。

2)在第一步产生一个chunk实例之后,会调用chunk.allocate(normCapacity)来真正进行内存分配。由于chunk是由一系列的Page构成,即Page才是最终分配内存的东西,因此如果是第一次在这个chunk的subpages[subpageIdx]所表示的Page上分配如小于512字节的tiny或[512,pageSize)的small内存,则需要通过new创建一个PoolSubpage实例(在allocateSubpage方法可以看到这一点),并且此PoolSubpage实例在初始化之后,会添加到tinySubpagePools或smallSubpagePools中(在PoolSubpage的构造函数中看到这一点),用于以后分配同样块大小的内存,即下次再有分配同样大小内存需求时,直接从tinySubpagePools或smallSubpagePools获取对应的subpage进行分配。

    PoolChunk.java
    long allocate(int normCapacity) 
        if ((normCapacity & subpageOverflowMask) != 0)  // >= pageSize
            return allocateRun(normCapacity);
         else 
            return allocateSubpage(normCapacity);
        
    
    private long allocateSubpage(int normCapacity) 
        int d = maxOrder; // subpages are only be allocated from pages i.e., leaves
        int id = allocateNode(d);
        if (id < 0) 
            return id;
        

        final PoolSubpage<T>[] subpages = this.subpages;
        final int pageSize = this.pageSize;

        freeBytes -= pageSize;

        int subpageIdx = subpageIdx(id);
        PoolSubpage<T> subpage = subpages[subpageIdx];
        if (subpage == null) //第一次分配时,则产生一个Page实例,并添加到tinySubpagePools或smallSubpagePools中
            subpage = new PoolSubpage<T>(this, id, runOffset(id), pageSize, normCapacity);
            subpages[subpageIdx] = subpage;
         else 
            subpage.init(normCapacity);
        
        return subpage.allocate();
     

上面的 chunk.allocate和 allocateSubpage在博文Netty源码分析:PoolChunk有详细的介绍,这里不再介绍。

在allocateNormal方法中调用了c.initBuf(buf, handle, reqCapacity)方法,调用链如下:

    void initBuf(PooledByteBuf<T> buf, long handle, int reqCapacity) 
        int memoryMapIdx = (int) handle;
        int bitmapIdx = (int) (handle >>> Integer.SIZE);
        if (bitmapIdx == 0) 
            byte val = value(memoryMapIdx);
            assert val == unusable : String.valueOf(val);
            buf.init(this, handle, runOffset(memoryMapIdx), reqCapacity, runLength(memoryMapIdx));
         else 
            initBufWithSubpage(buf, handle, bitmapIdx, reqCapacity);
        
    

    private void initBufWithSubpage(PooledByteBuf<T> buf, long handle, int bitmapIdx, int reqCapacity) 
        assert bitmapIdx != 0;

        int memoryMapIdx = (int) handle;

        PoolSubpage<T> subpage = subpages[subpageIdx(memoryMapIdx)];
        assert subpage.doNotDestroy;
        assert reqCapacity <= subpage.elemSize;

        buf.init(
            this, handle,
            runOffset(memoryMapIdx) + (bitmapIdx & 0x3FFFFFFF) * subpage.elemSize, reqCapacity, subpage.elemSize);
     
PooledByteBuf.java    

    void init(PoolChunk<T> chunk, long handle, int offset, int length, int maxLength) 
        assert handle >= 0;
        assert chunk != null;

        this.chunk = chunk;
        this.handle = handle;
        memory = chunk.memory;
        this.offset = offset;
        this.length = length;
        this.maxLength = maxLength;
        setIndex(0, 0);
        tmpNioBuf = null;
     

以上函数的功能为:将前面通过PoolSubpage所分配的结果转换为offset等参数然后初始化这个buf就ok了。

小结

看的有点粗,但大致思想总算是弄明白了

需要明白两点:

1、PoolArena通过6个PoolChunkList来管理PoolChunk,而每个PoolChunk由N个PoolSubpage构成,即将PoolChunk的里面底层实现 T memory分成N段,每段就是一个PoolSubpage。

2、当用户申请一个Buf时,使用chunk的某个Page来分配内存干的事情就是一点:确定Buf在chunk底层 T memory 内存的偏移量offset。然后初始化在这个Buf上就可以了。

如果你对这个也感兴趣,建议对跟几遍博文开头的测试代码。

参考资料

1、http://www.jianshu.com/p/4856bd30dd56

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

netty内存算法小析(下)

Netty 内存管理: PooledByteBufAllocator & PoolArena 代码探险[1]

nettybuffer源码学习2

PoolArena

Netty源码分析--内存模型(下)

Netty之旅四你一定看得懂的Netty客户端启动源码分析!