Netty 中的内存分配浅析( 三 )


从结构上来看,PoolArena 中主要包含三部分子内存池:
tinySubpagePools;
smallSubpagePools;
一系列的 PoolChunkList 。
tinySubpagePools 和 smallSubpagePools 都是 PoolSubpage 的数组,数组长度分别为 32 和 4 。
PoolChunkList 则主要是一个容器,其内部可以保存一系列的 PoolChunk 对象,并且,Netty 会根据内存使用率的不同,将 PoolChunkList 分为不同等级的容器 。
abstract class PoolArena<T> implements PoolArenaMetric {enum SizeClass {Tiny,Small,Normal}// 该参数指定了tinySubpagePools数组的长度,由于tinySubpagePools每一个元素的内存块差值为16,// 因而数组长度是512/16,也即这里的512 >>> 4static final int numTinySubpagePools = 512 >>> 4; //表示该PoolArena的allocatorfinal PooledByteBufAllocator parent;//表示PoolChunk中由Page节点构成的二叉树的最大高度,默认11private final int maxOrder;//page的大小,默认8Kfinal int pageSize;// 指定了叶节点大小8KB是2的多少次幂,默认为13,该字段的主要作用是,在计算目标内存属于二叉树的 // 第几层的时候,可以借助于其内存大小相对于pageShifts的差值,从而快速计算其所在层数final int pageShifts;//默认16MBfinal int chunkSize;// 由于PoolSubpage的大小为8KB=8196,因而该字段的值为 // -8192=>=> 1111 1111 1111 1111 1110 0000 0000 0000 // 这样在判断目标内存是否小于8KB时,只需要将目标内存与该数字进行与操作,只要操作结果等于0,// 就说明目标内存是小于8KB的,这样就可以判断其是应该首先在tinySubpagePools或smallSubpagePools // 中进行内存申请final int subpageOverflowMask;// 该参数指定了smallSubpagePools数组的长度,默认为4final int numSmallSubpagePools;//tinySubpagePools用来分配小于512 byte的Pageprivate final PoolSubpage<T>[] tinySubpagePools;//smallSubpagePools用来分配大于等于512 byte且小于pageSize内存的Pageprivate final PoolSubpage<T>[] smallSubpagePools;//用来存储用来分配给大于等于pageSize大小内存的PoolChunk//存储内存利用率50-100%的chunkprivate final PoolChunkList<T> q050;//存储内存利用率25-75%的chunkprivate final PoolChunkList<T> q025;//存储内存利用率1-50%的chunkprivate final PoolChunkList<T> q000;//存储内存利用率0-25%的chunkprivate final PoolChunkList<T> qInit;//存储内存利用率75-100%的chunkprivate final PoolChunkList<T> q075;//存储内存利用率100%的chunkprivate final PoolChunkList<T> q100; //堆内存(heap buffer)static final class HeapArena extends PoolArena<byte[]> {}//堆外直接内存(direct buffer)static final class DirectArena extends PoolArena<ByteBuffer> {}}如上所示,PoolArena 是由多个 PoolChunk 组成的大块内存区域,而每个 PoolChun k则由多个 Page 组成 。当需要分配的内存小于 Page 的时候,为了节约内存采用 PoolSubpage 实现小于 Page 大小内存的分配 。在PoolArena 中为了保证 PoolChunk 空间的最大利用化,按照 PoolArena 中各 个PoolChunk 已使用的空间大小将其划分为 6 类:
0-25%1-50%25-75%50-100%75-100%100%PoolArena 维护了一个 PoolChunkList 组成的双向链表,每个 PoolChunkList 内部维护了一个 PoolChunk 双向链表 。分配内存时,PoolArena 通过在 PoolChunkList 找到一个合适的 PoolChunk,然后从 PoolChunk 中分配一块内存 。
下面来看 PoolArena 是如何分配内存的:
private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {// 将需要申请的容量格式为 2^Nfinal int normCapacity = normalizeCapacity(reqCapacity);// 判断目标容量是否小于8KB,小于8KB则使用tiny或small的方式申请内存if (isTinyOrSmall(normCapacity)) { // capacity < pageSizeint tableIdx;PoolSubpage<T>[] table;boolean tiny = isTiny(normCapacity);// 判断目标容量是否小于512字节,小于512字节的为tiny类型的if (tiny) { // < 512// 将分配区域转移到 tinySubpagePools 中if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {// was able to allocate out of the cache so move onreturn;}// 如果无法从当前线程缓存中申请到内存,则尝试从tinySubpagePools中申请,这里tinyIdx()方法// 就是计算目标内存是在tinySubpagePools数组中的第几号元素中的tableIdx = tinyIdx(normCapacity);table = tinySubpagePools;} else {// 如果目标内存在512byte~8KB之间,则尝试从smallSubpagePools中申请内存 。这里首先从// 当前线程的缓存中申请small级别的内存,如果申请到了,则直接返回if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {// was able to allocate out of the cache so move onreturn;}tableIdx = smallIdx(normCapacity);table = smallSubpagePools;}// 获取目标元素的头结点final PoolSubpage<T> head = table[tableIdx];// 这里需要注意的是,由于对head进行了加锁,而在同步代码块中判断了s != head,// 也就是说PoolSubpage链表中是存在未使用的PoolSubpage的,因为如果该节点已经用完了,// 其是会被移除当前链表的 。也就是说只要s != head,那么这里的allocate()方法// 就一定能够申请到所需要的内存块synchronized (head) {// s != head就证明当前PoolSubpage链表中存在可用的PoolSubpage,并且一定能够申请到内存,// 因为已经耗尽的PoolSubpage是会从链表中移除的final PoolSubpage<T> s = head.next;// 如果此时 subpage 已经被分配过内存了执行下文,如果只是初始化过,则跳过该分支if (s != head) {// 从PoolSubpage中申请内存assert s.doNotDestroy && s.elemSize == normCapacity;// 通过申请的内存对ByteBuf进行初始化long handle = s.allocate();assert handle >= 0;// 初始化 PoolByteBuf 说明其位置被分配到该区域,但此时尚未分配内存s.chunk.initBufWithSubpage(buf, handle, reqCapacity);// 对tiny类型的申请数进行更新if (tiny) {allocationsTiny.increment();} else {allocationsSmall.increment();}return;}}// 走到这里,说明目标PoolSubpage链表中无法申请到目标内存块,因而就尝试从PoolChunk中申请allocateNormal(buf, reqCapacity, normCapacity);return;}// 走到这里说明目标内存是大于8KB的,那么就判断目标内存是否大于16M,如果大于16M,// 则不使用内存池对其进行管理,如果小于16M,则到PoolChunkList中进行内存申请if (normCapacity <= chunkSize) {// 小于16M,首先到当前线程的缓存中申请,如果申请到了则直接返回,如果没有申请到,// 则到PoolChunkList中进行申请if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {// was able to allocate out of the cache so move onreturn;}allocateNormal(buf, reqCapacity, normCapacity);} else {// 对于大于16M的内存,Netty不会对其进行维护,而是直接申请,然后返回给用户使用allocateHuge(buf, reqCapacity);}}


推荐阅读