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的主要内容,如果未能解决你的问题,请参考以下文章