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相关文章推荐
- Netty学习之旅------源码分析Netty内存池分配机制初探--PoolArena、PoolChunk、PoolSubpage等数据结构分析
- Netty 4 源码分析——结构概览
- Netty学习之旅------再谈线程模型之源码分析NioEventLoopGroup、SingleThreadEventExecutor
- Netty 源码分析(三):服务器端的初始化和注册过程
- Netty源码分析:图解Pipeline、Handler、Context
- 【Netty源码分析】客户端connect服务端过程
- 【Netty源码分析】Reactor线程模型
- Netty源码分析之番外篇【Java NIO的前生今世】
- Netty源码分析之零【分析环境搭建】
- 【Netty4.X】Netty源码分析之NioEventLoopGroup(五)
- Netty学习之旅----源码分析netty服务端初始化流程(Reactor主从模式实现)
- Netty5源码分析(四) -- 事件分发模型
- 源码之下无秘密 ── 做最好的 Netty 源码分析教程
- netty源码分析之ChannelPipeline
- Netty 源码分析之 EventLoop(二) (最重要)
- netty源码分析一
- netty4.0.x源码分析—bootstrap
- netty4.0.x源码分析—ChannelPipeline