您的位置:首页 > 其它

Netty源码分析:PoolArena

2017-10-16 21:55 453 查看

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: