如何理解netty的内存管理-PoolThreadCache

当多个线程使用同时同一个 PoolArena 分配内存时,因为存在竞争关系,所以会导致内存分配性能下降。为了减少冲突,PooledByteBufAllocator 会提供多个 PoolArena,并通过 PoolThreadCache 分配给每个 FastThread 线程,同一个线程多次分配释放的过程中,还会使用到缓存,以降低多次内存分配的压力。

PoolThreadCache

PoolArena的数量是多少?

PooledByteBufAllocator 默认会创建多个 PoolArenaheapArenadirectArena 数量计算的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
final int defaultMinNumArena = NettyRuntime.availableProcessors() * 2;

// heapArena
DEFAULT_NUM_HEAP_ARENA = Math.max(0,
SystemPropertyUtil.getInt(
"io.netty.allocator.numHeapArenas",
(int) Math.min(
defaultMinNumArena,
runtime.maxMemory() / defaultChunkSize / 2 / 3)));

// directArena
DEFAULT_NUM_DIRECT_ARENA = Math.max(0,
SystemPropertyUtil.getInt(
"io.netty.allocator.numDirectArenas",
(int) Math.min(
defaultMinNumArena,
PlatformDependent.maxDirectMemory() / defaultChunkSize / 2 / 3)));

通常 runtime.maxMemory()PlatformDependent.maxDirectMemory() 的值都比较大,因此 DEFAULT_NUM_HEAP_ARENADEFAULT_NUM_DIRECT_ARENA 的值默认与处理器的数量有关 NettyRuntime.availableProcessors() * 2

PoolThreadCache与PooledByteBufAllocator的关系

PooledByteBufAllocator 内部有一个 PoolThreadLocalCache 类型的属性,它继承自 FastThreadLocal<PoolThreadCache>,是一个 FastThreadLocal 类型的变量,当第一次调用它的 get 方法时会执行初始化逻辑,创建一个 PoolThreadCache 类型的实例并保存到 FastThread 中。

在创建 PoolThreadCache 实例时,需要从 PooledByteBufAllocatorPoolArena 数组中选择一个最少使用的。

1
2
final PoolArena<byte[]> heapArena = leastUsedArena(heapArenas);
final PoolArena<ByteBuffer> directArena = leastUsedArena(directArenas);

如果需要为线程使用缓存,则会在 PoolThreadCache 内部创建 MemoryRegionCache 类型的数组,当线程内分配的内存需要回收时会保存到这里。

1
2
3
final PoolThreadCache cache = new PoolThreadCache(
heapArena, directArena, tinyCacheSize, smallCacheSize, normalCacheSize,
DEFAULT_MAX_CACHED_BUFFER_CAPACITY, DEFAULT_CACHE_TRIM_INTERVAL);

PoolThreadCache的内部结构

PoolThreadCache 内部有 tiny、small、normal 三种类型的缓存,分别保存在 MemoryRegionCache 类型的数组中,根据底层实现是 heapMemory 或 directMemory 分别保存,因此共有 6 个 MemoryRegionCache 类型的数组。

此外还有两个从 PooledByteBufAllocator 获取到的 PoolArena ,分别用于分配 heapMemory 和 directMemory 类型的内存。

当通过 PoolThreadCache 分配内存时,首先会尝试使用对应类型的缓存进行分配,若分配失败再尝试从 PoolArena 分配,使用完的内存在释放时会归还到对应缓存中。

MemoryRegionCache的内部结构

MemoryRegionCache 内部主要是一个 Entry 类型的队列,当回收内存时就保存到这个队列中。此外还有一个 sizeClass 变量,用于保存内存大小的类型:tiny、small、normal。

Entry 封装了 PoolChunk 以及内存分配的信息:一个 long 类型的变量 handler,可计算出 PoolChunk 中用于定位 PoolSubpagememoryMapId,以及 PoolSubpage 中用于定位内存位置的 bitMapId