当前位置: 首页> 教育> 大学 > Netty内存池管理

Netty内存池管理

时间:2025/7/18 6:45:23来源:https://blog.csdn.net/white_pure/article/details/140832475 浏览次数:0次

文章目录

    • Netty内存池管理
      • PoolArena
      • PoolChunkList
      • PoolChunk
      • PoolSubpage
      • PoolThreadCache
      • 内存分配流程

Netty内存池管理

PooledByteBufAllocatorNetty提供的内存池实现。它用于分配和管理ByteBuf对象,减少频繁的内存分配和释放操作,提高性能。内存池将内存分为不同大小的块,每个大小对应一个内存池。内存池中的块被分为多个层级,包括ChunkPageSubpage

ChunkNetty向操作系统申请内存的单位,所有的内存分配操作也是基于Chunk完成的,Chunk可以理解为Page的集合,每个Chunk默认大小为16MPageChunk用于管理内存的单位,Netty中的Page的大小为8K,不要与Linux中的内存页Page混淆了。假如我们需要分配64K的内存,需要在Chunk中选取8个Page进行分配。

Subpage负责Page内的内存分配,假如我们分配的内存大小远小于Page,直接分配一个Page会造成严重的内存浪费,所以需要将Page划分为多个相同的子块进行分配,这里的子块就相当于Subpage。按照TinySmall两种内存规格,SubPage的大小也会分为两种情况。在Tiny场景下,最小的划分单位为16B,按16B依次递增,16B32B48B …… 496B;在Small场景下,总共可以划分为512B1024B2048B4096B四种情况。Subpage没有固定的大小,需要根据用户分配的缓冲区大小决定,例如分配1K的内存时,Netty会把一个Page等分为8个1KSubpage

PoolArena

PoolArenaPooledByteBufAllocator内部用于实际管理内存块的组件。PoolArena的数据结构包含两个PoolSubpage数组和六个PoolChunkList,两个PoolSubpage数组分别存放TinySmall类型的内存块,六个PoolChunkList 分别存储不同利用率的Chunk,构成一个双向循环链表。其中PoolSubpage用于分配小于8K的内存,PoolChunkList用于分配大于8K的内存。

PoolSubpage也是按照TinySmall两种内存规格,设计了tinySubpagePoolssmallSubpagePools两个数组,在Tiny场景下,内存单位最小为16B,按16B依次递增,共32种情况,Small场景下共分为512B1024B2048B4096B四种情况,分别对应两个数组的长度大小,每种粒度的内存单位都由一个PoolSubpage进行管理。假如我们分配20B大小的内存空间,也会向上取整找到32BPoolSubpage节点进行分配。

PoolChunkList用于Chunk场景下的内存分配,PoolArena中初始化了六个PoolChunkList,分别为qInitq000q025q050q075q100,它们分别代表不同的内存使用率。

  • qInit,内存使用率为0 ~ 25%Chunk
  • q000,内存使用率为1 ~ 50%Chunk
  • q025,内存使用率为25% ~ 75%Chunk
  • q050,内存使用率为50% ~ 100%Chunk
  • q075,内存使用率为75% ~ 100%Chunk
  • q100,内存使用率为100%Chunk

六种类型的PoolChunkList除了qInit,它们之间形成了双向链表。qInit用于存储初始分配的PoolChunk,因为在第一次内存分配时,PoolChunkList中并没有可用的PoolChunk,所以需要新创建一个PoolChunk 并添加到qInit列表中。qInit中的PoolChunk即使内存被完全释放也不会被回收,避免PoolChunk的重复初始化工作。其余类型的PoolChunkList中的PoolChunk当内存被完全释放后,PoolChunk从链表中移除,对应分配的内存也会被回收。

在使用PoolChunkList分配内存时,也就是分配大于8K的内存,其链表的访问顺序是q050->q025->q000->qInit->q075,遍历检查PoolChunkList中是否有PoolChunk可以用于内存分配。

private void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||q075.allocate(buf, reqCapacity, normCapacity)) {return;}PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);boolean success = c.allocate(buf, reqCapacity, normCapacity);assert success;qInit.add(c);
}

这是一个折中的选择,在频繁分配内存的场景下,如果从q000开始,会有大部分的PoolChunk面临频繁的创建和销毁,造成内存分配的性能降低。如果从q050开始,会使PoolChunk的使用率范围保持在中间水平,降低了PoolChunk被回收的概率,从而兼顾了性能。

PoolChunkList

PoolChunkList负责管理多个PoolChunk的生命周期,同一个PoolChunkList中存放内存使用率相近的PoolChunk,这些PoolChunk同样以双向链表的形式连接在一起。因为PoolChunk经常要从PoolChunkList中删除,并且需要在不同的PoolChunkList中移动,所以双向链表是管理PoolChunk时间复杂度较低的数据结构。

每个PoolChunkList都有内存使用率的上下限,minUsagemaxUsage,当PoolChunk进行内存分配后,如果使用率超过maxUsage,那么PoolChunk会从当前PoolChunkList移除,并移动到下一个PoolChunkList。同理,PoolChunk中的内存发生释放后,如果使用率小于minUsage,那么PoolChunk会从当前PoolChunkList移除,并移动到前一个PoolChunkList

PoolChunk

Netty内存的分配和回收都是基于PoolChunk完成的,PoolChunk是真正存储内存数据的地方,每个PoolChunk的默认大小为16M

final class PoolChunk<T> implements PoolChunkMetric {final PoolArena<T> arena;final T memory; // 存储的数据private final byte[] memoryMap; // 满二叉树中的节点是否被分配,数组大小为 4096private final byte[] depthMap; // 满二叉树中的节点高度,数组大小为 4096private final PoolSubpage<T>[] subpages; // PoolChunk 中管理的 2048 个 8K 内存块private int freeBytes; // 剩余的内存大小PoolChunkList<T> parent;PoolChunk<T> prev;PoolChunk<T> next;// 省略其他代码
}

PoolChunk可以理解为Page的集合,Page只是一种抽象的概念,实际在NettyPage所指的是PoolChunk所管理的子内存块,每个子内存块采用PoolSubpage表示。Netty会使用伙伴算法将PoolChunk分配成2048个Page,最终形成一颗满二叉树,二叉树中所有子节点的内存都属于其父节点管理。

PoolSubpage

它的主要作用是管理内存池中的小内存块,在分配的内存大小小于一个8K时,会使用PoolSubpage进行管理。

final class PoolSubpage<T> implements PoolSubpageMetric {final PoolChunk<T> chunk;private final int memoryMapIdx; // 对应满二叉树节点的下标private final int runOffset; // PoolSubpage 在 PoolChunk 中 memory 的偏移量private final long[] bitmap; // 记录每个小内存块的状态// 与 PoolArena 中 tinySubpagePools 或 smallSubpagePools 中元素连接成双向链表PoolSubpage<T> prev;PoolSubpage<T> next;int elemSize; // 每个小内存块的大小private int maxNumElems; // 最多可以存放多少小内存块:8K/elemSizeprivate int numAvail; // 可用于分配的内存块个数// 省略其他代码
}

PoolSubpage通过管理小于PageSize的内存块来优化内存分配。它从PoolChunk中预留一块连续内存区域,并将其划分为多个小块。每个PoolSubpage内部维护一个空闲块链表。当需要分配内存时,PoolSubpage会检查其内存区域中的空闲块,并从中选择一个可用的块进行分配。这样通过细化管理和复用小块内存,PoolSubpage能够高效地处理小块内存的分配请求,减少内存碎片化并提高性能。

PoolThreadCache

PoolThreadCache是本地线程缓存,内存分配时,首先尝试从线程本地缓存PoolThreadCache中获取内存块,如果缓存中没有,才会从全局内存池中获取。每个线程有自己的PoolThreadCache,可以快速分配和回收内存块,减少对全局内存池的争用。PoolThreadCache缓存TinySmallNormal三种类型的数据,而且根据堆内和堆外内存的类型进行了区分:

final class PoolThreadCache {final PoolArena<byte[]> heapArena;final PoolArena<ByteBuffer> directArena;private final MemoryRegionCache<byte[]>[] tinySubPageHeapCaches;private final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches;private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;private final MemoryRegionCache<byte[]>[] normalHeapCaches;private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;// 省略其他代码
}

PoolThreadCache中有一个重要的数据结构,MemoryRegionCacheMemoryRegionCache有三个重要的属性,分别为queuesizeClasssizeMemoryRegionCache实际就是一个队列,当内存释放时,将内存块加入队列当中,下次再分配同样规格的内存时,直接从队列中取出空闲的内存块。PoolThreadCache将不同规格大小的内存都使用单独的MemoryRegionCache维护。

内存分配流程

Netty的内存分配流程首先从PoolThreadCache中尝试分配内存。对于小于8K的内存请求,Netty首先检查本地线程缓存。如果缓存中没有足够的内存块,则会向PoolArena请求。对于大于8K的内存请求,Netty直接向PoolArena请求内存,不经过本地线程缓存。PoolArena使用PoolChunk来管理较大的内存块,通过伙伴算法和二叉树结构进行内存分配。对于小内存请求,PoolArena使用PoolSubpage来管理,按位图记录内存块的使用情况。释放的内存块会被缓存到PoolThreadCache中,定期进行整理,并在线程退出时释放。

具体来说:

  1. 分配请求处理:当应用程序需要分配内存时,Netty首先检查是否可以从 PoolThreadCache中获得所需的内存块。PoolThreadCacheNetty为每个线程维护的缓存,它用于存储和管理小块内存。这种缓存机制目的是提高内存分配的速度,减少线程间的竞争。
  2. 线程缓存检查:如果 PoolThreadCache 中有足够的空闲内存块,Netty将直接从缓存中分配内存。这种方式比从全局内存池中分配内存要快,因为它避免了对全局内存池的访问和管理开销。线程缓存的使用可以显著减少内存分配和释放的延迟。
  3. 请求PoolArena:当线程缓存中的内存不足以满足请求时,Netty将向PoolArena请求内存PoolArena 负责管理整个内存池中的内存块,它会根据内存块的大小选择适当的管理策略。
  4. PoolArena内存分配:PoolArena 处理内存分配时,会根据内存块的大小选择不同的管理策略。对于较小的内存块,PoolArena 使用 PoolSubpage 进行内存分配。PoolSubpage 使用位图来跟踪和管理内存块的使用状态,来减少内存碎片。对于较大的内存块,PoolArena 使用 PoolChunkPoolChunk通过伙伴算法或其他内存管理策略来处理大块内存。PoolChunk 负责分配较大的内存区域,能够应对不同的内存请求。
  5. 内存分配和回收:内存块从PoolArenaPoolSubpagePoolChunk中分配后,会被返回给应用程序使用。释放内存时,内存块将被归还到相应的内存管理区域。根据内存块的大小和当前的缓存状态,内存块可能会被归还到线程缓存中,或者直接归还到 PoolArena 中进行进一步管理。
  6. 线程缓存整理:为了保持线程缓存的有效性,Netty 定期对 PoolThreadCache 中的内存块进行整理。这一过程可以减少内存碎片,提高内存使用效率。当线程结束时,与该线程相关的线程缓存内存也会被释放和整理。
关键字:Netty内存池管理

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: