如何理解netty的内存管理-PoolThreadCache
当多个线程使用同时同一个 PoolArena
分配内存时,因为存在竞争关系,所以会导致内存分配性能下降。为了减少冲突,PooledByteBufAllocator
会提供多个 PoolArena
,并通过 PoolThreadCache
分配给每个 FastThread
线程,同一个线程多次分配释放的过程中,还会使用到缓存,以降低多次内存分配的压力。
PoolArena的数量是多少?
PooledByteBufAllocator
默认会创建多个 PoolArena
,heapArena
和 directArena
数量计算的代码如下:
1 | final int defaultMinNumArena = NettyRuntime.availableProcessors() * 2; |
通常 runtime.maxMemory()
和 PlatformDependent.maxDirectMemory()
的值都比较大,因此 DEFAULT_NUM_HEAP_ARENA
与 DEFAULT_NUM_DIRECT_ARENA
的值默认与处理器的数量有关 NettyRuntime.availableProcessors() * 2
。
PoolThreadCache与PooledByteBufAllocator的关系
PooledByteBufAllocator
内部有一个 PoolThreadLocalCache
类型的属性,它继承自 FastThreadLocal<PoolThreadCache>
,是一个 FastThreadLocal
类型的变量,当第一次调用它的 get 方法时会执行初始化逻辑,创建一个 PoolThreadCache
类型的实例并保存到 FastThread
中。
在创建 PoolThreadCache
实例时,需要从 PooledByteBufAllocator
的 PoolArena
数组中选择一个最少使用的。
1 | final PoolArena<byte[]> heapArena = leastUsedArena(heapArenas); |
如果需要为线程使用缓存,则会在 PoolThreadCache
内部创建 MemoryRegionCache
类型的数组,当线程内分配的内存需要回收时会保存到这里。
1 | final PoolThreadCache cache = new PoolThreadCache( |
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
中用于定位 PoolSubpage
的 memoryMapId
,以及 PoolSubpage
中用于定位内存位置的 bitMapId
。
- 2021-06-30
最近花时间研究了下 Netty 的内存管理实现,感觉挺有意思的,但也明显地感觉到,这部分功能要比其他功能模块更难理解。在这个过程中,我一直在想,如果能有这么一张图,能够说明内存管理的模块组成、各个模块之间的关系以及其实现原理,那么我就可以以更短的时间读懂相关源码了。因此,我将自己的一些理解画成了图。如果你也对这块内容感兴趣,兴许这篇文章能有所帮助。
- 2019-10-19
最近遇到一个 netty 的 OutOfDirectMemoryError 报错,是在分配 direct memory 时内存不足导致的,看了下报错提示,要分配的内存大小为 16M,剩余的空间不足。这里 max direct memory 大约有 7G,于是就有一个疑问,这个值是怎么设置的?