昨天,我彻底搞懂了Netty内存分配策略( 四 )


当需要分配的内存小于 Page 的时候,为了节约内存采用 PoolSubpage 实现小于 Page 大小内存的分配 。
在 PoolArena 中为了保证 PoolChunk 空间的最大利用化,按照 PoolArena 中各 个 PoolChunk 已使用的空间大小将其划分为六类:

  • qInit:存储内存利用率 0-25% 的 chunk
  • q000:存储内存利用率 1-50% 的 chunk
  • q025:存储内存利用率 25-75% 的 chunk
  • q050:存储内存利用率 50-100% 的 chunk
  • q075:存储内存利用率 75-100%的 chunk
  • q100:存储内存利用率 100%的 chunk
PoolArena 维护了一个 PoolChunkList 组成的双向链表,每个 PoolChunkList 内部维护了一个 PoolChunk 双向链表 。
分配内存时,PoolArena 通过在 PoolChunkList 找到一个合适的 PoolChunk,然后从 PoolChunk 中分配一块内存 。
下面来看 PoolArena 是如何分配内存的:
private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {  // 将需要申请的容量格式为 2^N  final int normCapacity = normalizeCapacity(reqCapacity);  // 判断目标容量是否小于8KB,小于8KB则使用tiny或small的方式申请内存  if (isTinyOrSmall(normCapacity)) { // capacity < pageSize    int 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 on        return;      }      // 如果无法从当前线程缓存中申请到内存,则尝试从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 on        return;      }      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 on      return;    }    allocateNormal(buf, reqCapacity, normCapacity);  } else {    // 对于大于16M的内存,Netty不会对其进行维护,而是直接申请,然后返回给用户使用    allocateHuge(buf, reqCapacity);  }}


推荐阅读