[嵌入式系统-253]:内存管理:内存堆的碎片化问题、种类与控制算法

张开发
2026/4/14 18:36:14 15 分钟阅读

分享文章

[嵌入式系统-253]:内存管理:内存堆的碎片化问题、种类与控制算法
存堆的碎片化就像是停车场堆长期使用后留下的“后遗症”。正如之前的比喻如果管理不当虽然总空地很大但连一辆大巴车都停不进去。针对这个问题我们结合最新的资料为你梳理了堆内存碎片化的种类、成因以及核心的控制算法。 内存碎片化的两大种类内存碎片主要分为“看得见”的外部碎片和“看不见”的内部碎片。1. 外部碎片 (External Fragmentation)定义这是最直观的碎片。指堆内存中未被使用的空闲内存被已分配的内存块分割成许多不连续的小块。现象总空闲内存很大比如 100MB但最大连续空闲块很小比如 2MB。当程序申请一个 5MB 的内存时虽然总空间足够但因为找不到连续的 5MB 空间而分配失败。成因频繁地分配和释放不同大小的内存块。例如先分配 A(100B)再分配 B(200B)释放 A此时产生了一个 100B 的空洞若下次申请 150B这个空洞就无法利用。2. 内部碎片 (Internal Fragmentation)定义指已分配的内存块内部有一部分空间是用户没用到、但被分配器强行占用的浪费空间。现象你申请了 20 字节但分配器实际上给了你 32 字节多出的 12 字节就是内部碎片。成因内存对齐 (Alignment)CPU 访问内存有对齐要求如 8 字节或 16 字节对齐。为了对齐编译器或分配器会在数据之间或末尾填充Padding空白字节。分配器元数据分配器为了管理内存块记录大小、状态等通常会在内存块头部或尾部附加信息这也算作内部开销。分级分配在内存池或 TCMalloc 中如果你申请 17 字节而该级别的最小块是 32 字节那么剩余的 15 字节就是内部碎片。⚙️ 核心控制算法与管理策略为了解决上述问题操作系统和高级分配器如 glibc 的 ptmalloc, jemalloc采用了多种算法。1. 空闲块搜索算法 (解决外部碎片)当需要分配内存时如何在空闲链表中找到合适的一块首次适应算法 (First Fit)原理从链表头开始找找到第一个“足够大”的空闲块就分配。优缺点速度快但容易在链表头部留下大量难以利用的微小碎片。最佳适应算法 (Best Fit)原理遍历所有空闲块找一个“刚好”能满足需求的最小块。优缺点保留了大块内存但会产生极小的、无法使用的碎片且遍历开销大。伙伴系统 (Buddy System)原理将内存按 2 的幂次方如 4KB, 8KB, 16KB...进行分级管理。如果申请 3KB就分配 4KB 的块。合并机制这是它的杀手锏。当释放一个块时系统会检查它的“伙伴”地址相邻的兄弟块是否也空闲。如果是两者立即合并成一个大块。这极大地缓解了外部碎片。2. 内存分片与分级 (解决碎片与性能)现代高性能分配器如jemalloc,tcmalloc的核心策略是“化整为零分类管理”。大小分级 (Size Classes)将对象分为“小对象”如 256B、“中等对象”和“大对象”。小对象使用固定的内存池Slab/Cache分配。例如所有 32 字节的请求都从一个专门存 32 字节块的链表中取。这彻底消除了小对象的外部碎片但会产生少量内部碎片因为实际大小可能略小于分级大小。大对象直接使用mmap 或堆顶分配避免污染小对象的缓存。线程缓存 (Thread Cache)为每个线程维护一个私有的内存缓存。线程分配小内存时直接从私有缓存拿无需加锁。这不仅解决了多线程锁竞争还减少了线程间内存交错释放导致的外部碎片。3. 内存紧凑 (Compaction)原理当碎片过多时强行将内存中所有存活的对象移动到一端将所有空闲空间合并到另一端。应用这在C/C 的malloc中很难实现因为指针移动会导致野指针但在带有垃圾回收 (GC)的语言如 Java, Go, C#中非常普遍。GC 会在回收时自动“整理”堆内存。 总结碎片化控制策略对比表格策略/算法针对问题核心思想典型应用伙伴系统外部碎片按 2 的幂次方分割释放时自动合并相邻块。Linux 物理内存管理Slab/分级分配外部碎片 性能预先切分固定大小的块如 16B, 32B同类对象复用。jemalloc,tcmalloc, Redis内存对齐硬件性能牺牲少量空间内部碎片换取 CPU 访问速度。所有现代编译器/分配器内存紧凑外部碎片移动对象物理上合并空闲空间。Java (G1, ZGC), Go, .NET内存池外部碎片 性能应用程序层自定义分配器隔离特定对象的分配。游戏引擎高频交易系统一句话总结控制内存碎片的核心在于“以空间换时间”和“分类隔离”——通过允许少量的内部碎片对齐/分级来换取极低的外部碎片和极快的分配速度。

更多文章