当前位置: 首页 > news >正文

Netty从0到1系列之Netty内存管理【下】

文章目录

  • Netty内存管理【下】
      • 2.3.3 PoolChunk内存分配
      • 2.3.4 PoolArena总结
      • 2.3.5 PoolChunkList
      • 2.3.6 PoolSubpage
      • 2.3.7 PoolThreadCache & MemoryRegionCache
    • 2.4 内存分配原理
      • 2.4.1 Page级别的内存分配
      • 2.4.2 Subpage 级别的内存分配
      • 2.4.3 PoolThreadCache 的内存分配
      • 2.4.4 内存回收原理


推荐阅读:

【01】Netty从0到1系列之I/O模型
【02】Netty从0到1系列之NIO
【03】Netty从0到1系列之Selector
【04】Netty从0到1系列之Channel
【05】Netty从0到1系列之Buffer(上)
【06】Netty从0到1系列之Buffer(下)
【07】Netty从0到1系列之零拷贝技术
【08】Netty从0到1系列之整体架构、入门程序
【09】Netty从0到1系列之EventLoop
【10】Netty从0到1系列之EventLoopGroup
【11】Netty从0到1系列之Future
【12】Netty从0到1系列之Promise
【13】Netty从0到1系列之Netty Channel
【14】Netty从0到1系列之ChannelFuture
【15】Netty从0到1系列之CloseFuture
【16】Netty从0到1系列之Netty Handler
【17】Netty从0到1系列之Netty Pipeline【上】
【18】Netty从0到1系列之Netty Pipeline【下】
【19】Netty从0到1系列之Netty ByteBuf【上】
【20】Netty从0到1系列之Netty ByteBuf【中】
【21】Netty从0到1系列之Netty ByteBuf【下】
【22】Netty从0到1系列之Netty 逻辑架构【上】
【23】Netty从0到1系列之Netty 逻辑架构【下】
【24】Netty从0到1系列之Netty 启动细节分析
【25】Netty从0到1系列之Netty 线程模型【上】
【26】Netty从0到1系列之Netty 线程模型【下】
【27】Netty从0到1系列之Netty ChannelPipeline
【28】Netty从0到1系列之Netty ChannelHandler
【29】Netty从0到1系列之Netty拆包、粘包【1】
【30】Netty从0到1系列之Netty拆包、粘包【2】
【31】Netty从0到1系列之Netty拆包、粘包【3】
【32】Netty从0到1系列之Netty拆包、粘包【4】
【33】Netty从0到1系列之Netty拆包、粘包【5】
【34】Netty从0到1系列之动态从内存分配】
【35】Netty从0到1系列之writeAndFlush原理分析】
【36】Netty从0到1系列之Netty内存管理【上】】


Netty内存管理【下】

2.3.3 PoolChunk内存分配

Netty 内存的分配和回收都是基于 PoolChunk 完成的,PoolChunk 是真正存储内存数据的地方,每个 PoolChunk 的默认大小为 16M,首先我们看下 PoolChunk 数据结构的定义:

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 只是一种抽象的概念,实际在 Netty 中 Page 所指的是 PoolChunk 所管理的子内存块,每个子内存块采用 PoolSubpage 表示。Netty 会使用伙伴算法将 PoolChunk 分配成 2048 个 Page, 最终形成一颗满二叉树,二叉树中所有子节点的内存都属于其父节点管理,如下图所示。

PoolChunk 用于 Chunk 场景下的内存分配【用于分配大于8KB内存】;

PoolChunk 是 Netty 向 JVM 申请的一大块连续内存(例如 16MB)的抽象。

  • 它是进行页级别(Page-Level) 分配的基本单位。

  • Netty 使用一种高效的 Buddy 算法完全二叉树(Complete Binary Tree) 来管理 PoolChunk 中的内存分配。一个 16MB 的 Chunk 默认由 2048 个 8KB 的 Page 组成。

  • 这棵二叉树的深度为 11(从 0 开始计算),每个节点记录着其下属子树中可用的 Page 数量。

PoolArean初始化了6PoolChunkList, 分别是qInit、q000、q025、q050、q075、q100, 分别代表不同的内存使用率,如下所示:

  • 🔏 qInit,内存使用率为 0 ~ 25% 的 Chunk。
  • 🉑 q000,内存使用率为 1 ~ 50% 的 Chunk。
  • 💃 q025,内存使用率为 25% ~ 75% 的 Chunk。
  • 🌓 q050, 内存使用率为 50% ~ 100% 的 Chunk。
  • 🌔 q075,内存使用率为 75% ~ 100% 的 Chunk。
  • 🥓 q100,内存使用率为 100% 的 Chunk。
private final PoolChunkList<T> q050;
private final PoolChunkList<T> q025;
private final PoolChunkList<T> q000;
private final PoolChunkList<T> qInit;
private final PoolChunkList<T> q075;
private final PoolChunkList<T> q100;q100 = new PoolChunkList<T>(this, null, 100, Integer.MAX_VALUE, sizeClass.chunkSize);
q075 = new PoolChunkList<T>(this, q100, 75, 100, sizeClass.chunkSize);
q050 = new PoolChunkList<T>(this, q075, 50, 100, sizeClass.chunkSize);
q025 = new PoolChunkList<T>(this, q050, 25, 75, sizeClass.chunkSize);
q000 = new PoolChunkList<T>(this, q025, 1, 50, sizeClass.chunkSize);
qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25, sizeClass.chunkSize);

六种类型的 PoolChunkList 除了 qInit,它们之间都形成了双向链表:

在这里插入图片描述

Chunk 内存使用率的变化,Netty 会重新检查内存的使用率并放入对应的 PoolChunkList,所以 PoolChunk 会在不同的 PoolChunkList 移动

  • qInit 和 q000 为什么需要设计成两个,是否可以合并成一个?
    • qInit 用于存储初始分配的 PoolChunk
      • 因为在第一次内存分配时,PoolChunkList 中并没有可用的 PoolChunk,所以需要新创建一个 PoolChunk 并添加到 qInit 列表中。
      • qInit 中的 PoolChunk 即使内存被完全释放也不会被回收,避免 PoolChunk 的重复初始化工作
  • q000 则用于存放内存使用率为 1 ~ 50% 的 PoolChunk,q000 中的 PoolChunk 内存被完全释放后,PoolChunk 从链表中移除,对应分配的内存也会被回收.

【特别注意】: 在分配大于 8K 的内存时,其链表的访问顺序是 q050 -> q025 -> q000 -> qInit -> q075, 遍历检查 PoolChunkList 中是否有 PoolChunk 可以用于内存分配.

private void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int sizeIdx, PoolThreadCache threadCache) {// 断言当前线程持有锁assert lock.isHeldByCurrentThread();// 尝试从 q050 队列分配内存if (q050.allocate(buf, reqCapacity, sizeIdx, threadCache) ||// 尝试从 q025 队列分配内存q025.allocate(buf, reqCapacity, sizeIdx, threadCache) ||// 尝试从 q000 队列分配内存q000.allocate(buf, reqCapacity, sizeIdx, threadCache) ||// 尝试从 qInit 队列分配内存qInit.allocate(buf, reqCapacity, sizeIdx, threadCache) ||// 尝试从 q075 队列分配内存q075.allocate(buf, reqCapacity, sizeIdx, threadCache)) {return;}// 如果上述队列都无法分配内存,则添加一个新的块(chunk)PoolChunk<T> c = newChunk(sizeClass.pageSize, sizeClass.nPSizes, sizeClass.pageShifts, sizeClass.chunkSize);// 尝试从新创建的块中分配内存boolean success = c.allocate(buf, reqCapacity, sizeIdx, threadCache);// 断言分配成功assert success;// 将新创建的块添加到 qInit 队列中qInit.add(c);
}

【特别注意】: 为什么会优先选择 q050,而不是从 q000 开始呢?

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

分配过程

在这里插入图片描述

分配过程(譬如申请16KB,即2 Pages)

  • 从根节点开始查找。

  • 检查左子节点(值=1024)是否 >= 2?是,继续向下查找。

  • 检查左子节点的左子节点(值=512)是否 >= 2?是,继续向下…

  • 直到在深度为 3 的某个节点(管理着 16个Page)找到可用的 2 个连续 Page。

  • 将该节点标记为已使用(值置为0),并递归更新其所有父节点的值(父节点值 = max(左子节点值, 右子节点值))。

结合 PoolChunk 的结构图,我们介绍一下 PoolChunk 中几个重要的属性

  • depthMap 用于存放节点所对应的高度, 例如第 2048 个节点 depthMap[1025] = 10
  • memoryMap 用于记录二叉树节点的分配信息,memoryMap 初始值与 depthMap 是一样的,随着节点被分配,不仅节点的值会改变,而且会递归遍历更新其父节点的值,父节点的值取两个子节点中最小的值。
  • subpages 对应上图中 PoolChunk 内部的 Page0、Page1、Page2 … Page2047
  • Netty 中并没有 Page 的定义,直接使用 PoolSubpage 表示。
    • 当分配的内存小于 8K 时,PoolChunk 中的每个 Page 节点会被划分成为更小粒度的内存块进行管理,小内存块同样以 PoolSubpage 管理。
    • 小内存的分配场景下,会首先找到对应的 PoolArena ,然后根据计算出对应的 tinySubpagePools 或者 smallSubpagePools 数组对应的下标,如果对应数组元素所包含的 PoolSubpage 链表不存在任何节点,那么将创建新的 PoolSubpage 加入链表中。

2.3.4 PoolArena总结

Netty 内存规格的分类,PoolArena 对应实现了 Subpage 和 Chunk 中的内存分配

  • PoolSubpage 用于分配小于 8K 的内存
    • 🎟 Tiny
      • tinySubpagePools[]
        • 单位最小为 16B,按 16B 依次递增,共 32 种情况
    • ⛵️ Small
      • smallSubpagePools[]
        • 512B、1024B、2048B、4096B四种情况
  • PoolChunkList 用于分配大于 8K 的内存
    • 共有6PoolChunkList
      • qInit、q000、q025、q050、q075、q100
      • 分配大于8K内存的时候,从q050开始分配
        • q050->q025->q000->qInit->q075

2.3.5 PoolChunkList

Arena 不会直接管理零散的 PoolChunk,而是通过 PoolChunkList 来管理。PoolChunkList 是一个 PoolChunk 的链表,但这些链表是根据 Chunk 的内存使用率来划分的。

Netty 定义了多个 PoolChunkList,例如:

  • q000: 使用率 0% - 25%
  • q025: 使用率 25% - 50%
  • q050: 使用率 50% - 75%
  • q075: 使用率 75% - 100%
  • q100: 使用率 100%

分配策略:分配请求会优先在 q050 -> q025 -> q000 -> q075 -> q100 这样的顺序中寻找合适的 Chunk。这样可以优先使用使用率适中的 Chunk,提高内存使用率并减少碎片。

释放策略:当一个 Chunk 的内存被释放导致使用率下降时,它会被移动到更低使用率的 PoolChunkList 中。反之,当分配导致使用率上升时,它可能会被移动到更高使用率的列表中。

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

在这里插入图片描述

当 PoolChunk 进行内存分配后,如果使用率超过 maxUsage,那么 PoolChunk 会从当前 PoolChunkList 移除,并移动到下一个 PoolChunkList。

同理,PoolChunk 中的内存发生释放后,如果使用率小于 minUsage,那么 PoolChunk 会从当前 PoolChunkList 移除,并移动到前一个 PoolChunkList。

Netty 初始化的六个 PoolChunkList,每个 PoolChunkList 的上下限都有交叉重叠的部分,如下图所示。因为 PoolChunk 需要在 PoolChunkList 不断移动,如果每个 PoolChunkList 的内存使用率的临界值都是恰好衔接的,例如 1 ~ 50%、50% ~ 75%,那么如果 PoolChunk 的使用率一直处于 50% 的临界值,会导致 PoolChunk 在两个 PoolChunkList 不断移动,造成性能损耗。

在这里插入图片描述

2.3.6 PoolSubpage

PoolSubpage 应该有了一些认识,在小内存分配的场景下,即分配的内存大小小于一个 Page 8K,会使用 PoolSubpage 进行管理。首先看下 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 中每个属性的含义都比较清晰易懂;

❓第一个就是 PoolSubpage 是如何记录内存块的使用状态的呢?

  • PoolSubpage 通过位图 bitmap 记录子内存是否已经被使用bit 的取值为 0 或者 1

在这里插入图片描述

2.3.7 PoolThreadCache & MemoryRegionCache

PoolThreadCache 顾名思义,对应的是 jemalloc 中本地线程缓存的意思。那么 PoolThreadCache 是如何被使用的呢?

当内存释放时,与 jemalloc 一样,Netty 并没有将缓存归还给 PoolChunk,而是使用 PoolThreadCache 缓存起来,当下次有同样规格的内存分配时,直接从 PoolThreadCache 取出使用即可PoolThreadCache 缓存 Tiny、Small、Normal 三种类型的数据,而且根据堆内和堆外内存的类型进行了区分

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 有三个重要的属性,分别为 queue,sizeClass 和 size,下图是不同内存规格所对应的 MemoryRegionCache 属性取值范围:

QueuesizeClasssize
Queue<Entry<T>>tiny (0 ~ 512B)N*16B
Queue<Entry<T>>samll (512B ~ 8K)512B、1K、2K、4K
Queue<Entry<T>>normal (8k ~ 16M)8K、16K、32K

MemoryRegionCache 实际就是一个队列,当内存释放时,将内存块加入队列当中,下次再分配同样规格的内存时,直接从队列中取出空闲的内存块。

PoolThreadCache 将不同规格大小的内存都使用单独的 MemoryRegionCache 维护,如下图所示,图中的每个节点都对应一个 MemoryRegionCache

2.4 内存分配原理

Netty 中负责线程分配的组件有两个:PoolArena和PoolThreadCache。PoolArena 是多个线程共享的,每个线程会固定绑定一个 PoolArena,PoolThreadCache 是每个线程私有的缓存空间,如下图所示。

在这里插入图片描述

  • PoolArena 中管理的内存单位为 PoolChunk【大小16MB, 16384KB】,每个 PoolChunk 会被划分为 2048【16384/8=2048】 个 8K 的 Page.
    • 申请的内存【大于8KB】时, PoolChunk会以Page为单位进行内存分配.
    • 申请的内存【小于8KB】时, 会由PoolSubpage管理粒度更小的内存分配.
  • PoolArena 分配的内存被释放后, 不会立即会还给 PoolChunk, 而且会缓存在本地私有缓存 PoolThreadCache 中,在下一次进行内存分配时,会优先从 PoolThreadCache 中查找匹配的内存块。

总结:

  • 分配内存大于 8K 时,PoolChunk 中采用的 Page 级别的内存分配策略
  • 分配内存小于 8K 时由 PoolSubpage 负责管理的内存分配策略。
  • 分配内存小于 8K 时,为了提高内存分配效率,由 PoolThreadCache 本地线程缓存提供的内存分配。

2.4.1 Page级别的内存分配

每个 PoolChunk 默认大小为 16M,PoolChunk 是通过伙伴算法管理多个 Page,每个 PoolChunk 被划分为 2048 个 Page.
在这里插入图片描述

假如用户需要依次申请 8K、16K、8K 的内存,通过这里例子我们详细描述下 PoolChunk 如何分配 Page 级别的内存,方便大家理解伙伴算法的原理.

[!tip]

  • PoolChunk 分配 Page 主要分为三步
    • 首先根据分配内存大小计算二叉树所在节点的高度
    • 然后查找对应高度中是否存在可用节点
    • 如果分配成功则减去已分配的内存大小得到剩余可用空间

2.4.2 Subpage 级别的内存分配

为了提高内存分配的利用率,在分配小于 8K 的内存时,PoolChunk 不在分配单独的 Page,而是将 Page 划分为更小的内存块,由 PoolSubpage 进行管理。

long allocate() {if (numAvail == 0 || !doNotDestroy) {return -1;}final int bitmapIdx = getNextAvail();if (bitmapIdx < 0) {removeFromPool(); // Subpage appear to be in an invalid state. Remove to prevent repeated errors.throw new AssertionError("No next available bitmap index found (bitmapIdx = " + bitmapIdx + "), " +"even though there are supposed to be (numAvail = " + numAvail + ") " +"out of (maxNumElems = " + maxNumElems + ") available indexes.");}int q = bitmapIdx >>> 6;int r = bitmapIdx & 63;assert (bitmap[q] >>> r & 1) == 0;bitmap[q] |= 1L << r;if (-- numAvail == 0) {removeFromPool();}return toHandle(bitmapIdx);
}

2.4.3 PoolThreadCache 的内存分配

我们知道 PoolArena 分配的内存被释放时,Netty 并没有将缓存归还给 PoolChunk,而是使用 PoolThreadCache 缓存起来,当下次有同样规格的内存分配时,直接从 PoolThreadCache 取出使用即可。所以下面我们从 PoolArena#allocate() 的源码中看下 PoolThreadCache 是如何使用的。

private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {final int normCapacity = normalizeCapacity(reqCapacity);if (isTinyOrSmall(normCapacity)) { // capacity < pageSizeint tableIdx;PoolSubpage<T>[] table;boolean tiny = isTiny(normCapacity);if (tiny) { // < 512if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {return;}tableIdx = tinyIdx(normCapacity);table = tinySubpagePools;} else {if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {return;}tableIdx = smallIdx(normCapacity);table = smallSubpagePools;}// 省略其他代码}if (normCapacity <= chunkSize) {if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {return;}synchronized (this) {allocateNormal(buf, reqCapacity, normCapacity);++allocationsNormal;}} else {allocateHuge(buf, reqCapacity);}
}
  • 对申请的内存大小做向上取整,例如 20B 的内存大小会取整为 32B。
  • 当申请的内存大小小于 8K 时,分为 Tiny 和 Small 两种情况,分别都会优先尝试从 PoolThreadCache 分配内存,如果 PoolThreadCache 分配失败,才会走 PoolArena 的分配流程。
  • 当申请的内存大小大于 8K,但是小于 Chunk 的默认大小 16M,属于 Normal 的内存分配,也会优先尝试从 PoolThreadCache 分配内存,如果 PoolThreadCache 分配失败,才会走 PoolArena 的分配流程。
  • 当申请的内存大小大于 Chunk 的 16M,则不会经过 PoolThreadCache,直接进行分配

2.4.4 内存回收原理

当用户线程释放内存时会将内存块缓存到本地线程的私有缓存 PoolThreadCache 中,这样在下次分配内存时会提高分配效率,但是当内存块被用完一次后,再没有分配需求,那么一直驻留在内存中又会造成浪费。接下来我们就看下 Netty 是如何实现内存释放的呢?直接跟进下 PoolThreadCache 的源码.

private boolean allocate(MemoryRegionCache<?> cache, PooledByteBuf buf, int reqCapacity) {if (cache == null) {return false;}// 默认每执行 8192 次 allocate(),就会调用一次 trim() 进行内存整理boolean allocated = cache.allocate(buf, reqCapacity);if (++ allocations >= freeSweepAllocationThreshold) {allocations = 0;trim();}return allocated;
}void trim() {trim(tinySubPageDirectCaches);trim(smallSubPageDirectCaches);trim(normalDirectCaches);trim(tinySubPageHeapCaches);trim(smallSubPageHeapCaches);trim(normalHeapCaches);
}

Netty 记录了 allocate() 的执行次数,默认每执行 8192 次,就会触发 PoolThreadCache 调用一次 trim() 进行内存整理,会对 PoolThreadCache 中维护的六个 MemoryRegionCache 数组分别进行整理.

public final void trim() {int free = size - allocations;allocations = 0;// We not even allocated all the number that areif (free > 0) {free(free, false);}
}private int free(int max, boolean finalizer) {int numFreed = 0;for (; numFreed < max; numFreed++) {Entry<T> entry = queue.poll();if (entry != null) {freeEntry(entry, finalizer);} else {// all clearedreturn numFreed;}}return numFreed;
}

通过 size - allocations 衡量内存分配执行的频繁程度,其中 size 为该 MemoryRegionCache 对应的内存规格大小,size 为固定值,例如 Tiny 类型默认为 512。allocations 表示 MemoryRegionCache 距离上一次内存整理已经发生了多少次 allocate 调用,当调用次数小于 size 时,表示 MemoryRegionCache 中缓存的内存块并不常用,从队列中取出内存块依次释放。

此外 Netty 在线程退出的时候还会回收该线程的所有内存,PoolThreadCache 重载了 finalize() 方法,在销毁前执行缓存回收的逻辑,对应源码如下:

@Override
protected void finalize() throws Throwable {try {super.finalize();} finally {free(true);}
}void free(boolean finalizer) {if (freed.compareAndSet(false, true)) {int numFreed = free(tinySubPageDirectCaches, finalizer) +free(smallSubPageDirectCaches, finalizer) +free(normalDirectCaches, finalizer) +free(tinySubPageHeapCaches, finalizer) +free(smallSubPageHeapCaches, finalizer) +free(normalHeapCaches, finalizer);if (numFreed > 0 && logger.isDebugEnabled()) {logger.debug("Freed {} thread-local buffer(s) from thread: {}", numFreed,Thread.currentThread().getName());}if (directArena != null) {directArena.numThreadCaches.getAndDecrement();}if (heapArena != null) {heapArena.numThreadCaches.getAndDecrement();}}
}

线程销毁时 PoolThreadCache 会依次释放所有 MemoryRegionCache 中的内存数据,其中 free 方法的核心逻辑与之前内存整理 trim 中释放内存的过程是一致的,有兴趣的同学可以自行翻阅源码。

到此为止,整个 Netty 内存池的分配和释放原理我们已经分析完了,其中巧妙的设计思路以及源码细节的实现,都是非常值得我们学习的宝贵资源。

内存分配总结

  • 分四种内存规格管理内存,分别为 Tiny、Samll、Normal、Huge,PoolChunk 负责管理 8K 以上的内存分配,PoolSubpage 用于管理 8K 以下的内存分配。当申请内存大于 16M 时,不会经过内存池,直接分配。
  • 设计了本地线程缓存机制 PoolThreadCache,用于提升内存分配时的并发性能。用于申请 Tiny、Samll、Normal 三种类型的内存时,会优先尝试从 PoolThreadCache 中分配。
  • PoolChunk 使用伙伴算法管理 Page,以二叉树的数据结构实现,是整个内存池分配的核心所在。
  • 每调用 PoolThreadCache 的 allocate() 方法到一定次数,会触发检查 PoolThreadCache 中缓存的使用频率,使用频率较低的内存块会被释放。
  • 线程退出时,Netty 会回收该线程对应的所有内存
http://www.dtcms.com/a/392996.html

相关文章:

  • 【使用函数求余弦COS函数的近似值】2022-11-27
  • 前端违规页面车主信息优化说明
  • 成功安装了 Anaconda3。要启动它,您有以下几种主要方式:方式一:通过“开始菜单”启动(最直接的方法)1. 点击您电脑屏幕左下角的 “开始菜单”(Win
  • flex布局实现导航栏横向滚动切换
  • 改进过程缺乏数据驱动会带来哪些后果
  • 实验1.1点亮led灯
  • 林粒粒的视频笔记13-数据清洗
  • Java进阶教程,全面剖析Java多线程编程,线程出让,笔记09
  • 大模型微调之 用LoRA微调Llama2(附代码)——李宏毅2025大模型作业5笔记-上
  • Matplotlib地理数据可视化技术详解:Cartopy与Basemap实战指南
  • wordpress 图片不显示 后台无法登陆的问题之一
  • TFS-2023《Local-Global Fuzzy Clustering With Anchor Graph》
  • Spring —— AOP
  • 讲一下ZooKeeper的持久化机制
  • 【Java后端】深入理解 Spring Security:从原理到实战
  • LeetCode:31.K个一组翻转链表
  • openharmony之系统亮度范围定制
  • 一种利用串口51单片机远程升级 OTA
  • Redis三种集群模式
  • C++ map_set封装
  • NW836NW884美光固态闪存NW885NW913
  • STM32计算步进电机转速
  • liboffice 全屏禁用工具栏
  • Photoshop - Photoshop 调整图像品质
  • 【CF】Day146——杂题 (递归 | 规律与操作)
  • PyTorch 中特征变换:卷积与转置卷积
  • HashMap底层原理详解:扩容、红黑树与ConcurrentHashMap的线程安全
  • autodl文件存储,文件同步,conda环境同步问题
  • 【ROS2】Begginer : CLI tools - 理解 ROS 2 话题
  • Java网络编程:从基础到实战