# BufferAllocator中的池化

从整体来说,分配缓冲区的时候Netty利用了缓存机制,尽最大可能复用重复的内存,并减少内存碎片。

Netty的池化通过PoolChunk来完成,可以认为每次申请Buffer时,都是通过某个PoolChunk中的共享内存来分配的。具体策略如下:

# PoolArena

对于PoolArena来说,其内部按照6级分位来划分内存空间。每一级单独维护缓存。

        q100 = new PoolChunkList(this, null, 100, Integer.MAX_VALUE, chunkSize);
        q075 = new PoolChunkList(this, q100, 75, 100, chunkSize);
        q050 = new PoolChunkList(this, q075, 50, 100, chunkSize);
        q025 = new PoolChunkList(this, q050, 25, 75, chunkSize);
        q000 = new PoolChunkList(this, q025, 1, 50, chunkSize);
        qInit = new PoolChunkList(this, q000, Integer.MIN_VALUE, 25, chunkSize);

6个chunkList形成一个双向链表,里面对应的内存分片会根据当前占用的空间大小进行动态调整。

q025中25和75的含义是,当前chunkList分配的单片最多占用75%的的chunkSize,最少占用25%的chunkSize。对于不满足的场景,会顺延到对应的ChunkList中。

  • 每次申请内存时,从head(init)开始申请,由于init中的最大内存量是很少的,因此如果申请失败,就转而到下一个(25)中申请。如果申请成功了,对应的chunk会再次检查自己是否还满足当前百分位,如果不满足,那么自动挪到对应的list中。
  • 每次释放内存时,和申请一样在释放后进行调整。

这样做可以保证每次都先从内存空间最大的chunk中申请内存,来保证空间利用率的最大化。

# PoolChunk

PoolChunk是PoolArena中的最小单元。

Chunk内部也有如下概念:

  • page表示内存分片的最小单位。
  • run表示page集合。
  • chunk表示run集合。
  • chunkSize=maxPages * pageSize

每次Buffer分配的时候,从上到下寻找第一个可分配的空间。

  A chunk has the following layout:

     /-----------------\
     | run             |
     |                 |
     |                 |
     |-----------------|
     | run             |
     |                 |
     |-----------------|
     | unalloctated    |
     | (freed)         |
     |                 |
     |-----------------|
     | subpage         |
     |-----------------|
     | unallocated     |
     | (freed)         |
     | ...             |
     | ...             |
     | ...             |
     |                 |
     |                 |
     |                 |
     \-----------------/

# 内存申请

申请策略保存在PoolArena的父类SizeClasses。

可以理解为是一个基于分组的内存分配策略。

sizeClasses中的内存分配,可以理解为每个分组都是上一个分组的2^n倍。因此group大小按照log方式保存。

这样的好处在于对于任意size的申请来说,首先可以通过这个表格对照来计算出最佳的大小和类型(subPage或run)。

 * sizeClasses: Complete table of [index, log2Group, log2Delta, nDelta, isMultiPageSize,
 *                 isSubPage, log2DeltaLookup] tuples.
 *     index: Size class index.
 *     log2Group: Log of group base size (no deltas added).
 *     log2Delta: Log of delta to previous size class.
 *     nDelta: Delta multiplier.
 *     isMultiPageSize: 'yes' if a multiple of the page size, 'no' otherwise.
 *     isSubPage: 'yes' if a subpage size class, 'no' otherwise.
 *     log2DeltaLookup: Same as log2Delta if a lookup table size class, 'no'
 *                      otherwise.
 *   (index, log2Group, log2Delta, nDelta, isMultiPageSize, isSubPage, log2DeltaLookup)
 * <p>
 *   ( 0,     4,        4,         0,       no,             yes,        4)
 *   ( 1,     4,        4,         1,       no,             yes,        4)
 *   ( 2,     4,        4,         2,       no,             yes,        4)
 *   ( 3,     4,        4,         3,       no,             yes,        4)
 * <p>
 *   ( 4,     6,        4,         1,       no,             yes,        4)
 *   ( 5,     6,        4,         2,       no,             yes,        4)
 *   ( 6,     6,        4,         3,       no,             yes,        4)
 *   ( 7,     6,        4,         4,       no,             yes,        4)
 * <p>
 *   ( 8,     7,        5,         1,       no,             yes,        5)
 *   ( 9,     7,        5,         2,       no,             yes,        5)
 *   ( 10,    7,        5,         3,       no,             yes,        5)
 *   ( 11,    7,        5,         4,       no,             yes,        5)
 *   ...
 *   ...
 *   ( 72,    23,       21,        1,       yes,            no,        no)
 *   ( 73,    23,       21,        2,       yes,            no,        no)
 *   ( 74,    23,       21,        3,       yes,            no,        no)
 *   ( 75,    23,       21,        4,       yes,            no,        no)
 * <p>
 *   ( 76,    24,       22,        1,       yes,            no,        no)

# 数据结构

handle:

是一个长数字,bit抽象成如下:

oooooooo ooooooos ssssssss ssssssue bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb
  • o:15bit,表示chunk中的page偏移量。
  • s:15bit,表示当前run中的page数量。
  • u:1bit,表示是否被使用。
  • e:1bit,表示是否是subPage。
  • b:32bit,表示subPage的bitmapIdx。如果不是subPage就是0.

runsAvailMap:

  • key: runOffset
  • value: handle

runsAvail:

优先队列的列表。每个优先队列管理相同大小的run。run通过offset设置优先级,所以每次都可以分配最小offset的run。

# 算法

allocateRun(size):

  1. 找到第一个满足要求的run
  2. 如果run的page比需要的多,run分裂,分裂后面的留给后续使用。

allocateSubpage(size):

  1. 找到第一个满足size没满的subpage。如果找到了直接返回,否则分配一个新的PoolSubpage。
  2. 执行SubPage的分配(allocate)。

free(handle, len, buffer)

  1. 如果释放的是subpage,那么释放当前标记。
  2. 如果subpage没被使用,或者是一个run,那么释放。
  3. 合并连续的可用run。
  4. 保存合并后的run。