Netty内存池中ChunkList详解
✍️ 第1章 ChunkList 概述
1.1 什么是 ChunkList
在 Netty 的内存池机制中,ChunkList 是一种按使用率分组管理 PoolChunk 的结构。
如果把整个内存池比作一个“快递物流系统”:
PoolArena 就像一个总调度中心;
PoolChunk 是一块完整的货仓;
ChunkList 就是“仓库使用率分类清单”,按照仓库利用率的高低,把仓库分配到不同的清单里,以便快速选择和调度。
这样,Netty 就能避免无序查找,提高分配与回收效率。
1.2 ChunkList 在内存池中的定位
Netty 通过 PoolArena → ChunkList → PoolChunk → Page/Subpage 的层级结构来完成内存管理。
PoolArena:顶层调度者,根据分配大小选择合适的 ChunkList。
ChunkList:分组管理,保证不同使用率的 Chunk 不会混淆。
PoolChunk:具体的内存块(默认16MB)。
Page/Subpage:更小粒度的分配单元(默认8KB Page,Subpage 用于小对象)。
因此,ChunkList 相当于 PoolChunk 的中间层“分类容器”。
1.3 ChunkList 与 PoolArena、PoolChunk 的关系
PoolArena 维护多个 ChunkList,分别对应不同使用率区间。
每个 ChunkList 内部是一个 双向链表,串联多个 PoolChunk。
PoolChunk 在分配/释放内存后,可能会从一个 ChunkList 迁移到另一个。
📌 示例结构(文字版流程图):
PoolArena ├── qInit (0-25%) │ └── [ChunkA] <-> [ChunkB] ├── q000 (1-50%) │ └── [ChunkC] ├── q025 (25-75%) │ └── [ChunkD] ├── q050 (50-100%) ├── q075 (75-100%) └── q100 (100%)
这样,Arena 就能快速定位到合适的 Chunk,而不用遍历所有 Chunk。
1.4 ChunkList 的核心设计目标
快速查找合适的 Chunk:通过分类减少无效遍历。
动态迁移:根据使用率上下限,自动迁移 Chunk,避免空间浪费。
减少内存碎片:分配与回收保持平衡,避免大量小碎片。
高并发优化:保证 Arena 下的多线程分配性能。
1章小结
ChunkList 是 PoolChunk 的“分组管理器”,根据使用率将 Chunk 分配到不同清单中。
它是 PoolArena 与 PoolChunk 的中间层,承担调度和优化作用。
通过动态迁移和阈值管理,ChunkList 提升了内存利用率和性能。
第2章 ChunkList 的分类与阈值机制
2.1 六类 ChunkList(qInit、q000、q025、q050、q075、q100)
在 Netty 的内存池中,ChunkList 被划分为六类,它们的区别就在于 内存使用率区间(usage range)。
ChunkList 名称 | 使用率范围 | 作用 |
---|---|---|
qInit | 0% ~ 25% | 新加入的空闲 Chunk,利用率最低 |
q000 | 1% ~ 50% | 轻度使用的 Chunk,适合小规模分配 |
q025 | 25% ~ 75% | 中等使用率的 Chunk,主要分配区 |
q050 | 50% ~ 100% | 高利用率 Chunk,接近饱和 |
q075 | 75% ~ 100% | 几乎满的 Chunk,可能随时迁移到 q100 |
q100 | 100% | 已满 Chunk,暂不可再分配 |
📌 类比:
可以把这 6 类 ChunkList 想象成一个“快递仓库的使用率分级系统”:
qInit = 新开仓库,货物很少;
q025/q050 = 中等繁忙仓库,随时可调度;
q075/q100 = 已经堆满的仓库;
q000 = 刚开始有点货物,还很空;
这样分组后,PoolArena 可以快速定位“最佳仓库”来放货(分配内存)。
2.2 minUsage / maxUsage 的计算逻辑
每个 PoolChunkList
都会设置 minUsage 和 maxUsage,决定该链表允许的 Chunk 使用率范围。
源码(简化版)如下:
final class PoolChunkList<T> {final int minUsage;final int maxUsage;PoolChunkList(PoolArena<T> arena, PoolChunkList<T> nextList, int minUsage, int maxUsage) {this.minUsage = minUsage;this.maxUsage = maxUsage;}
}
minUsage:该列表能容纳的 最低使用率阈值。
maxUsage:该列表能容纳的 最高使用率阈值。
例如:
q000 → minUsage = 1, maxUsage = 50
q025 → minUsage = 25, maxUsage = 75
📌 注意:
有些区间是重叠的(如 q000 的最大 50%,与 q025 的最小 25%),这是为了避免迁移抖动,保证迁移更平滑。
2.3 阈值迁移的触发条件
当一个 PoolChunk 的使用率发生变化时(分配或释放),它可能需要 迁移到其他 ChunkList。
源码中的关键逻辑(简化):
boolean move(PoolChunk<T> chunk) {int usage = chunk.usage();// 如果超过 maxUsage,迁移到下一个列表if (usage >= maxUsage) {return move0(chunk, nextList);}// 如果低于 minUsage,迁移到上一个列表if (usage < minUsage) {return move0(chunk, prevList);}return true; // 还在当前范围内,无需迁移
}
解释:
usage ≥ maxUsage → 向“更满”的 ChunkList 移动。
usage < minUsage → 向“更空”的 ChunkList 移动。
这样保证了每个 ChunkList 内的 Chunk 使用率大致稳定在一个区间。
2.4 内存使用率与性能的平衡
为什么要分成这么多层级?主要有两个目的:
快速查找合适的 Chunk
如果只用一个链表,Arena 在分配时需要遍历大量不合适的 Chunk。
分层后,Arena 可以优先选择中等使用率的 Chunk(比如 q025),减少查找成本。
减少内存碎片
如果所有新分配都来自“很空”的 Chunk(qInit),会造成很多半空的 Chunk,浪费内存。
通过阈值迁移,Netty 保证“中等繁忙的 Chunk”优先分配,减少内存碎片。
📌 类比:
如果把 Chunk 比作“电影院”,ChunkList 就像“观众上座率分组”:
0-25%(稀疏观众厅) → 不适合继续安排太多人,因为分散浪费;
25-75%(正常上座厅) → 最佳利用率,观众集中;
100%(满厅) → 不可再进人,只能去别的厅。
示例代码:查看 ChunkList 的阈值
我们可以写个小片段来模拟:
PoolChunkList<?> q000 = new PoolChunkList<>(arena, null, 1, 50);
PoolChunkList<?> q025 = new PoolChunkList<>(arena, q000, 25, 75);System.out.println("q000 范围: " + q000.minUsage + " ~ " + q000.maxUsage);
System.out.println("q025 范围: " + q025.minUsage + " ~ " + q025.maxUsage);
输出结果:
q000 范围: 1 ~ 50
q025 范围: 25 ~ 75
这能直观反映 Netty 是如何定义“分区阈值”的。
2章小结
ChunkList 分为六类,每类对应不同使用率范围。
通过 minUsage / maxUsage 阈值,Chunk 会在不同列表间迁移。
阈值设计有重叠,避免迁移抖动。
这种分层机制帮助 快速查找可用 Chunk,并且 减少内存碎片。
第3章 内存分配流程解析
3.1 PoolArena → ChunkList → PoolChunk 分配链路
在 Netty 内存池中,一次分配请求的流程大致如下:
应用请求内存↓
PoolArena(选择合适的 ChunkList)↓
ChunkList(在范围内找到合适的 PoolChunk)↓
PoolChunk(基于二叉树索引分配内存)↓
Page/Subpage(最终的分配结果)
📌 类比:
PoolArena = 调度员,决定去哪类仓库取货;
ChunkList = 仓库分类(半满仓 / 已满仓 / 空仓);
PoolChunk = 仓库本体;
Page/Subpage = 货架或格子(小的内存分配单元)。
3.2 allocate 方法源码解析
来看 PoolChunkList.allocate()
的核心源码(简化版):
boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {PoolChunk<T> cur = head;while (cur != null) {if (cur.allocate(buf, reqCapacity, normCapacity)) {// 分配成功if (cur.usage() >= maxUsage) {// 如果超过当前 ChunkList 的阈值,则迁移到 nextListremove(cur);nextList.add(cur);}return true;}cur = cur.next;}return false;
}
逻辑分解:
遍历链表:从当前 ChunkList 的
head
开始,依次尝试分配。调用 PoolChunk.allocate():具体的分配逻辑由 PoolChunk 完成(基于二叉树的索引)。
检查使用率:如果分配后
usage ≥ maxUsage
,就把该 Chunk 移动到下一个更满的 ChunkList。返回结果:如果成功就返回
true
,否则尝试下一个 Chunk。
3.3 Page / Subpage 的选择逻辑
Netty 会根据 请求容量大小 来决定分配 Page 还是 Subpage:
大对象(≥PageSize,默认8KB) → 直接分配 Page 级别。
小对象(<PageSize) → 分配 Subpage(内部进一步切分 Page)。
源码中 PoolChunk.allocate()
内部有类似逻辑:
long handle = allocateRun(normCapacity);
if (handle < 0) {handle = allocateSubpage(normCapacity);
}
📌 直观理解:
如果要一张“大桌子”(大对象),直接占用一个 Page。
如果只要“小凳子”(小对象),就在 Page 里切分出 Subpage 给你。
3.4 示例:一次内存分配的全链路过程
假设我们请求 16KB 的内存:
PoolArena
判断请求大小(16KB ≥ PageSize=8KB)。
选择合适的 ChunkList(比如 q025)。
ChunkList
遍历其中的 PoolChunk,找到能容纳 16KB 的。
PoolChunk
基于二叉树索引,找到两个连续 Page(2×8KB)。
标记为已分配。
结果返回
返回一个
PooledByteBuf
,包装这块内存。如果 PoolChunk 使用率升高,可能会迁移到下一个 ChunkList。
📌 流程图(文字描述):
请求 16KB↓
PoolArena → q025↓
PoolChunkD↓
二叉树索引找到 2 个 Page↓
返回 PooledByteBuf
示例代码:模拟内存分配
我们可以写一段小伪代码(模拟 Netty 行为):
// 模拟分配
PooledByteBuf<byte[]> buf = arena.allocate(16384); // 16KBSystem.out.println("分配的内存容量: " + buf.capacity());
System.out.println("分配来自的 Chunk: " + buf.chunk());
可能的日志输出:
分配的内存容量: 16384
分配来自的 Chunk: PoolChunk@1234 usage=40%
这说明分配成功,并且 Chunk 的使用率被更新。
3章小结
分配流程:PoolArena → ChunkList → PoolChunk → Page/Subpage。
PoolChunkList.allocate()
会遍历 Chunk,调用PoolChunk.allocate()
来完成分配。大对象用 Page,小对象用 Subpage。
分配完成后,如果 Chunk 使用率超过当前范围,就会迁移到下一个 ChunkList。
第4章 内存回收与 Chunk 迁移机制
4.1 free 方法源码解析
当应用释放内存时,最终会走到 PoolChunkList.free()
,再调用 PoolChunk.free()
完成回收。源码(简化):
boolean free(PoolChunk<T> chunk, long handle, ByteBuffer nioBuffer) {chunk.free(handle, nioBuffer);// 使用率降低后,检查是否需要迁移if (chunk.usage() < minUsage) {remove(chunk); // 从当前链表移除prevList.add(chunk); // 迁移到前一个 ChunkList(更空的)return false;}return true; // 保持在当前 ChunkList
}
📌 核心逻辑:
调用 PoolChunk.free() 回收内存。
更新 Chunk 使用率。
如果
usage < minUsage
,说明 Chunk 过于空闲,就 迁移到上一个 ChunkList。
4.2 内存回收后的使用率判断
举个例子:
当前 Chunk 在 q025 (25%-75%)。
回收一部分内存后,使用率下降到 15%。
低于
minUsage=25
,于是迁移到 q000 (1%-50%)。
这样一来,Arena 就知道:
q000 的 Chunk 适合存放小量分配;
q025 的 Chunk 始终保持在中等利用率,不会因为“半空”而被浪费。
4.3 Chunk 在不同 ChunkList 之间的迁移逻辑
我们来把分配(allocate)和回收(free)的迁移机制做个对比:
分配后 → usage ≥ maxUsage
Chunk 迁移到 nextList(更满的链表)。
回收后 → usage < minUsage
Chunk 迁移到 prevList(更空的链表)。
📌 直观流程(文字版):
分配内存:if usage >= maxUsage → nextList释放内存:if usage < minUsage → prevList
这样一来,所有 Chunk 都会动态流动,保持在一个“合适的利用率区间”。
4.4 示例:内存释放后的 ChunkList 变化
假设我们有一个 Chunk,初始在 q025:
分配了 4KB → usage = 30% → 仍在 q025。
分配到 60% → 仍在 q025。
分配到 80% → 超过
maxUsage=75%
→ 迁移到 q050。释放 40% → usage = 40% → 回到 q025。
再释放到 10% → usage < 25% → 迁移到 q000。
📌 类比电影院:
观众多了 → 换到“更满的分区”;
观众走了 → 回到“更空的分区”。
这样,Arena 分配时就能快速挑选最合适的影厅。
示例代码:模拟 free() 逻辑
// 模拟回收逻辑
boolean stillInList = q025.free(chunk, handle, nioBuffer);System.out.println("当前 Chunk 使用率: " + chunk.usage());
System.out.println("是否仍在 q025: " + stillInList);
可能的输出:
当前 Chunk 使用率: 18%
是否仍在 q025: false // 已迁移到 q000
这说明:释放后,Chunk 使用率降低,被迁移到更空的 ChunkList。
4章小结
free() 回收内存,更新 Chunk 的使用率。
如果 usage < minUsage,Chunk 就会迁移到更空的 ChunkList。
分配和回收结合,保证 Chunk 动态流动,维持内存利用率平衡。
这种机制减少了内存碎片,让 PoolArena 能快速找到合适的 Chunk。
第5章 PoolChunkList 源码深度解析
5.1 核心属性解析
PoolChunkList
维护了以下核心属性:
final class PoolChunkList<T> {final PoolChunkList<T> nextList; // 下一个 ChunkListfinal PoolChunkList<T> prevList; // 上一个 ChunkListPoolChunk<T> head; // 当前链表头final int minUsage; // 最低使用率阈值final int maxUsage; // 最高使用率阈值
}
属性作用:
属性 | 作用 |
---|---|
nextList | 指向更高使用率的 ChunkList,用于迁移 Chunk |
prevList | 指向更低使用率的 ChunkList,用于迁移 Chunk |
head | 当前 ChunkList 的第一个 Chunk,形成双向链表 |
minUsage / maxUsage | 控制 Chunk 使用率的迁移范围 |
📌 核心思想:PoolChunkList
是一个 双向链表 + 阈值管理器,通过 prevList
和 nextList
动态调节 Chunk 所在的链表。
5.2 add 方法解析
add()
用于将 Chunk 加入当前链表头:
void add(PoolChunk<T> chunk) {chunk.parent = this;if (head != null) {chunk.next = head;head.prev = chunk;}head = chunk;chunk.prev = null;
}
分步说明:
设置父列表:
chunk.parent = this
维护双向链表:
新 chunk 指向当前 head
head.prev 指向新 chunk
更新链表头:
head = chunk
断开前向指针:
chunk.prev = null
📌 类比:
每次加入链表都是 “头插法”,效率高,且保证新 Chunk 优先被分配。
5.3 remove 方法解析
remove()
用于将 Chunk 从链表中移除:
void remove(PoolChunk<T> chunk) {PoolChunk<T> next = chunk.next;PoolChunk<T> prev = chunk.prev;if (prev != null) {prev.next = next;} else {head = next; // 如果是头,更新 head}if (next != null) {next.prev = prev;}chunk.prev = null;chunk.next = null;chunk.parent = null;
}
分步说明:
保存 前后节点
更新前节点的 next 指向 chunk.next
更新后节点的 prev 指向 chunk.prev
如果 chunk 是链表头,更新 head
清空 chunk 的 prev/next/parent
📌 作用:彻底移除 Chunk,并断开所有引用,方便 GC 或迁移到其他 ChunkList。
5.4 move 方法解析
move()
用于在使用率变化时,将 Chunk 从当前链表迁移到适合的链表:
boolean move(PoolChunk<T> chunk) {int usage = chunk.usage();if (usage >= maxUsage) {remove(chunk);nextList.add(chunk);return false;}if (usage < minUsage) {remove(chunk);prevList.add(chunk);return false;}return true; // 使用率在范围内,无需迁移
}
分步说明:
获取使用率:
chunk.usage()
超过最大阈值 → 迁移到
nextList
低于最小阈值 → 迁移到
prevList
在阈值范围内 → 保持在当前列表
📌 核心价值:动态迁移机制,保证每个 Chunk 的使用率维持在最适区间,减少内存碎片。
5.5 内部双向链表维护机制
特点:
头插法加入 Chunk,保证新 Chunk 优先分配
remove 时会断开所有指针,避免链表残留引用
move 方法结合 add/remove,实现动态迁移
prevList / nextList 形成双向迁移链,确保使用率平衡
📌 类比:
ChunkList 是一个 流水线调度器,Chunk 在链表里“流动”,随着使用率变化上下游迁移。
5.6 示例代码:调试 Chunk 的迁移
// 创建 q025 和 q050 两个列表
PoolChunkList<?> q050 = new PoolChunkList<>(arena, q075, 50, 100);
PoolChunkList<?> q025 = new PoolChunkList<>(arena, q050, 25, 75, q000);// 新建一个 Chunk
PoolChunk<?> chunk = new PoolChunk<>(arena, 16 * 1024 * 1024); // 16MB
q025.add(chunk);System.out.println("添加到 q025,head: " + q025.head);// 模拟分配使使用率超过 maxUsage
chunk.setUsage(80);
q025.move(chunk);System.out.println("迁移后 q025 head: " + q025.head);
System.out.println("迁移后 q050 head: " + q050.head);
输出说明:
添加到 q025,head: PoolChunk@1234
迁移后 q025 head: null
迁移后 q050 head: PoolChunk@1234
说明:Chunk 成功从 q025 迁移到 q050,符合预期逻辑。
5章小结
核心属性:
head
,prevList
,nextList
,minUsage
,maxUsage
add/remove:维护双向链表,支持高效插入和删除
move:动态迁移 Chunk,保证链表中 Chunk 使用率在合理区间
双向链表机制:结合 prev/next,实现 Chunk 的灵活流动,减少碎片
第6章 ChunkList 的性能优化策略
6.1 减少内存碎片的设计思路
ChunkList 的分层管理和迁移机制,是 Netty 减少内存碎片的核心策略:
使用率分区
Chunk 按使用率划分到不同的 ChunkList(qInit、q000、q025 等)。
高利用率和低利用率的 Chunk 不混在同一个链表,避免分配碎片化。
动态迁移
分配后 usage 超过 maxUsage → 迁移到下一个更满的列表
回收后 usage 低于 minUsage → 迁移到前一个更空列表
这种机制保证链表中 Chunk 的使用率保持在最适区间。
二叉树索引分配
PoolChunk 内部使用完全二叉树管理 Page。
分配和释放操作通过二叉树快速找到合适的空闲块,进一步减少碎片。
📌 类比:
电影院座位管理:
半空座位集中分配 → 避免零散空座
动态迁移 → 让观众在座位分布均匀,减少散座浪费
6.2 动态迁移的性能优势
动态迁移带来的优势主要体现在以下几个方面:
快速分配
Arena 可以直接在最合适的 ChunkList 查找空闲 Chunk,而无需遍历所有 Chunk。
减少碎片
低使用率 Chunk 被集中管理,避免在链表中混杂大量空闲空间。
平衡负载
高并发分配时,Chunk 在不同链表间动态流动,避免单个 Chunk 被过度分配或过度空闲。
6.3 高并发场景下的内存利用率优化
在高并发场景(如秒杀系统)中:
问题:大量并发分配请求可能集中在少量 Chunk,导致热点 Chunk 使用率过高或过低,影响内存利用率。
解决方案:
ChunkList 分层 → 快速定位合适 Chunk
动态迁移 → 将热点 Chunk 移动到更高使用率列表,空闲 Chunk 移动到低使用率列表
PoolThreadCache → 本地线程缓存,减少 Arena 访问竞争
📌 流程(文字版):
高并发请求 →
Arena选择适合的ChunkList →
从链表头分配Chunk →
更新Chunk使用率 →
触发迁移(move) →
保持链表使用率均衡
6.4 对比直接内存分配与 JVM GC 的差异
特性 | ChunkList + Arena | 直接堆内存分配 / JVM GC |
---|---|---|
分配速度 | 高效,几乎 O(1) | 受 GC 和堆分配速度影响 |
内存碎片 | 可控,通过迁移和分层减少碎片 | 随对象生命周期碎片化严重 |
高并发 | 支持多线程,结合 PoolThreadCache 减少竞争 | 高并发可能导致 GC 压力大 |
可预测性 | 使用率明确,易于监控 | 不可控,依赖 JVM GC |
📌 小结:
ChunkList 提供高效、可控、低碎片的内存管理方案,特别适合网络 IO、高并发系统。
相比 JVM 默认堆分配,内存池机制能够显著减少 GC 压力,提高吞吐量。
示例代码:模拟高并发分配与迁移
// 模拟多线程高并发分配
ExecutorService executor = Executors.newFixedThreadPool(8);
for (int i = 0; i < 1000; i++) {executor.submit(() -> {PooledByteBuf<byte[]> buf = arena.allocate(4096); // 4KB// 模拟使用buf.clear();arena.free(buf); // 回收,触发可能的迁移});
}
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
运行后,可以观察 ChunkList 内的 Chunk 使用率动态变化
空闲 Chunk 自动迁移到低使用率链表,高使用率 Chunk 迁移到高使用率链表
6章小结
ChunkList 的性能优化核心在于分层 + 动态迁移 + 二叉树索引
高并发场景下,通过迁移和线程本地缓存减少竞争,提高吞吐量
相比直接堆内存分配或依赖 JVM GC,ChunkList 提供更可控、更高效的内存管理
内存碎片与 ChunkList 优化机制详解
1. 什么是内存碎片
内存碎片(Memory Fragmentation)是指 可用内存被分割成小块,无法满足连续大块分配需求 的现象。它主要分为两类:
外碎片(External Fragmentation)
内存总量足够,但可用的连续内存块太小,无法满足大对象分配。
例如:有 8KB、4KB、2KB 的空闲块,总共 14KB,但需要分配一个 12KB 的连续空间就失败了。
内碎片(Internal Fragmentation)
分配的内存比实际请求多,未被使用的部分造成浪费。
例如:请求 6KB 内存,但 PageSize=8KB,实际分配 8KB,多出的 2KB 不能使用。
2. Netty ChunkList 如何优化内存碎片
结合前面章节知识,Netty 的 ChunkList 机制优化内存碎片的思路主要有以下几点:
2.1 分层管理减少外碎片
Chunk 按使用率划分到不同的 ChunkList(qInit、q000、q025…)
优势:
中等使用率的 Chunk(q025/q050)集中处理大部分分配请求
空闲或过满的 Chunk 被隔离,减少查找不合适 Chunk 的开销
效果:避免了大量半空 Chunk 与低使用率 Chunk 混杂,减少了外碎片
📌 类比:电影院座位分区
半满区集中安排观众 → 避免零散空座,减少“外碎片”
2.2 动态迁移减少外碎片
分配后:如果 Chunk 使用率超过 maxUsage → 迁移到下一层
回收后:如果 Chunk 使用率低于 minUsage → 迁移到上一层
效果:Chunk 在链表中流动,保持使用率在合理区间
优势:
低使用率 Chunk 集中管理,便于小对象分配
高使用率 Chunk 集中管理,便于大对象分配
结果:减少链表中零散空闲块,降低外碎片风险
2.3 Page / Subpage 切分减少内碎片
小对象(< PageSize)通过 Subpage 切分 Page
大对象直接分配完整 Page
优势:
避免小对象占用整 Page 导致浪费
分配颗粒度合理,提高内存利用率
效果:降低了内碎片产生的概率
📌 举例:
请求 1KB → 在 Subpage 中分配,剩余空间可继续分配 1KB / 2KB …
请求 8KB → 分配整个 Page,避免多次小块零散占用
2.4 双向链表 + 阈值机制的综合作用
add / remove / move 保证链表中 Chunk 使用率均衡
prevList / nextList 形成动态迁移通道
效果:
分配和回收都在最合适的链表执行
减少分配失败和多余内存浪费
优势:整体内存池维持高利用率,同时降低碎片产生
3. 总结:为什么优化了内存碎片
优化机制 | 针对碎片类型 | 原理 |
---|---|---|
分层管理 | 外碎片 | 中等使用率 Chunk 集中,避免零散空闲块分布 |
动态迁移 | 外碎片 | Chunk 在链表间流动,保持使用率区间稳定 |
Page / Subpage 切分 | 内碎片 | 小对象在 Subpage 分配,避免浪费整 Page |
双向链表 | 外碎片 | 高效 add/remove,保证链表整齐、连续性好 |
📌 总结类比:
外碎片 → 座位零散分布,难以容纳大团体
内碎片 → 每人占了 1 个大座位,但只坐了一半
ChunkList 优化 → 合理分区、动态迁移和细粒度分配,使座位使用率高、零散空位少