Netty内存池中ChunkList详解

发布于:2025-08-20 ⋅ 阅读:(13) ⋅ 点赞:(0)

✍️ 第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 都会设置 minUsagemaxUsage,决定该链表允许的 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 内存使用率与性能的平衡

为什么要分成这么多层级?主要有两个目的:

  1. 快速查找合适的 Chunk

    • 如果只用一个链表,Arena 在分配时需要遍历大量不合适的 Chunk。

    • 分层后,Arena 可以优先选择中等使用率的 Chunk(比如 q025),减少查找成本。

  2. 减少内存碎片

    • 如果所有新分配都来自“很空”的 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 的阈值,则迁移到 nextList
                remove(cur);
                nextList.add(cur);
            }
            return true;
        }
        cur = cur.next;
    }
    return false;
}

逻辑分解:

  1. 遍历链表:从当前 ChunkList 的 head 开始,依次尝试分配。

  2. 调用 PoolChunk.allocate():具体的分配逻辑由 PoolChunk 完成(基于二叉树的索引)。

  3. 检查使用率:如果分配后 usage ≥ maxUsage,就把该 Chunk 移动到下一个更满的 ChunkList。

  4. 返回结果:如果成功就返回 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 的内存

  1. PoolArena

    • 判断请求大小(16KB ≥ PageSize=8KB)。

    • 选择合适的 ChunkList(比如 q025)。

  2. ChunkList

    • 遍历其中的 PoolChunk,找到能容纳 16KB 的。

  3. PoolChunk

    • 基于二叉树索引,找到两个连续 Page(2×8KB)。

    • 标记为已分配。

  4. 结果返回

    • 返回一个 PooledByteBuf,包装这块内存。

    • 如果 PoolChunk 使用率升高,可能会迁移到下一个 ChunkList。

📌 流程图(文字描述):

请求 16KB
   ↓
PoolArena → q025
   ↓
PoolChunkD
   ↓
二叉树索引找到 2 个 Page
   ↓
返回 PooledByteBuf

示例代码:模拟内存分配

我们可以写一段小伪代码(模拟 Netty 行为):

// 模拟分配
PooledByteBuf<byte[]> buf = arena.allocate(16384); // 16KB

System.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
}

📌 核心逻辑:

  1. 调用 PoolChunk.free() 回收内存。

  2. 更新 Chunk 使用率

  3. 如果 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:

  1. 分配了 4KB → usage = 30% → 仍在 q025。

  2. 分配到 60% → 仍在 q025。

  3. 分配到 80% → 超过 maxUsage=75% → 迁移到 q050。

  4. 释放 40% → usage = 40% → 回到 q025。

  5. 再释放到 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; // 下一个 ChunkList
    final PoolChunkList<T> prevList; // 上一个 ChunkList
    PoolChunk<T> head;               // 当前链表头
    final int minUsage;              // 最低使用率阈值
    final int maxUsage;              // 最高使用率阈值
}

属性作用:

属性 作用
nextList 指向更高使用率的 ChunkList,用于迁移 Chunk
prevList 指向更低使用率的 ChunkList,用于迁移 Chunk
head 当前 ChunkList 的第一个 Chunk,形成双向链表
minUsage / maxUsage 控制 Chunk 使用率的迁移范围

📌 核心思想:PoolChunkList 是一个 双向链表 + 阈值管理器,通过 prevListnextList 动态调节 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;
}

分步说明:

  1. 设置父列表chunk.parent = this

  2. 维护双向链表

    • 新 chunk 指向当前 head

    • head.prev 指向新 chunk

  3. 更新链表头head = chunk

  4. 断开前向指针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;
}

分步说明:

  1. 保存 前后节点

  2. 更新前节点的 next 指向 chunk.next

  3. 更新后节点的 prev 指向 chunk.prev

  4. 如果 chunk 是链表头,更新 head

  5. 清空 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; // 使用率在范围内,无需迁移
}

分步说明:

  1. 获取使用率chunk.usage()

  2. 超过最大阈值 → 迁移到 nextList

  3. 低于最小阈值 → 迁移到 prevList

  4. 在阈值范围内 → 保持在当前列表

📌 核心价值:动态迁移机制,保证每个 Chunk 的使用率维持在最适区间,减少内存碎片。


5.5 内部双向链表维护机制

特点:

  1. 头插法加入 Chunk,保证新 Chunk 优先分配

  2. remove 时会断开所有指针,避免链表残留引用

  3. move 方法结合 add/remove,实现动态迁移

  4. 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 减少内存碎片的核心策略:

  1. 使用率分区

    • Chunk 按使用率划分到不同的 ChunkList(qInit、q000、q025 等)。

    • 高利用率和低利用率的 Chunk 不混在同一个链表,避免分配碎片化。

  2. 动态迁移

    • 分配后 usage 超过 maxUsage → 迁移到下一个更满的列表

    • 回收后 usage 低于 minUsage → 迁移到前一个更空列表

    • 这种机制保证链表中 Chunk 的使用率保持在最适区间。

  3. 二叉树索引分配

    • PoolChunk 内部使用完全二叉树管理 Page。

    • 分配和释放操作通过二叉树快速找到合适的空闲块,进一步减少碎片。

📌 类比:

  • 电影院座位管理:

    • 半空座位集中分配 → 避免零散空座

    • 动态迁移 → 让观众在座位分布均匀,减少散座浪费


6.2 动态迁移的性能优势

动态迁移带来的优势主要体现在以下几个方面:

  1. 快速分配

    • Arena 可以直接在最合适的 ChunkList 查找空闲 Chunk,而无需遍历所有 Chunk。

  2. 减少碎片

    • 低使用率 Chunk 被集中管理,避免在链表中混杂大量空闲空间。

  3. 平衡负载

    • 高并发分配时,Chunk 在不同链表间动态流动,避免单个 Chunk 被过度分配或过度空闲。


6.3 高并发场景下的内存利用率优化

在高并发场景(如秒杀系统)中:

  • 问题:大量并发分配请求可能集中在少量 Chunk,导致热点 Chunk 使用率过高或过低,影响内存利用率。

  • 解决方案

    1. ChunkList 分层 → 快速定位合适 Chunk

    2. 动态迁移 → 将热点 Chunk 移动到更高使用率列表,空闲 Chunk 移动到低使用率列表

    3. 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)是指 可用内存被分割成小块,无法满足连续大块分配需求 的现象。它主要分为两类:

  1. 外碎片(External Fragmentation)

    • 内存总量足够,但可用的连续内存块太小,无法满足大对象分配。

    • 例如:有 8KB、4KB、2KB 的空闲块,总共 14KB,但需要分配一个 12KB 的连续空间就失败了。

  2. 内碎片(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 优化 → 合理分区、动态迁移和细粒度分配,使座位使用率高、零散空位少


网站公告

今日签到

点亮在社区的每一天
去签到