• 在程序运行时程序员使用动态内存分配器 (比如 malloc) 获得虚拟内存.
    • 数据结构的大小只有运行时才知道.
  • 动态内存分配器维护者一个进程的虚拟内存区域,称为堆.
    • 堆的向上增长:如果分配一个块,我们想要一个 10 个字节的块,然后检查这堆里面现在所有的空闲块,如果它最大只有 8 个字节的空闲块,那这时候发现没有空闲的块可以供我们使用,那这个堆就会向上增长,然后给它分配 10 个字节空闲块,这个堆的总的空间就会变大
  • 分配器将堆视为一组不同大小的块(blocks)的集合来维护,每个块要么是已分配的,要么是空闲的(就是可以往里面分配数据)
  • 分配器的类型
    • 显式分配器: 要求应用显式地释放任何已分配的快
      • 例如,C 语言中的 malloc 和 free
    • 隐式分配器: 应用检测到已分配块不再被程序所使用,就释放这个块
      • 比如 Java,ML 和 Lisp 等高级语言中的垃圾收集 (garbage collection)

动态内存分配实例

对齐:新申请的空间的字节数必须能被 8 整除,int 是 4 字节的,p2 申请的内存空间是 所以需要多申请一个块,也就是 6 个满足 对齐
空闲块:比如 p4 的申请

分配器

  • Applications 应用
    • 可以处理任意的分配( malloc)和释放(free)请求序列
    • 只能释放已分配的块
  • Allocators 分配器
    • 无法控制分配块的数量或大小
    • 立即响应 malloc 请求
      • 比如, 不允许分配器重新排列或者缓冲请求
    • 必须从空闲内存分配块
    • 必须对齐块,使得它们可以保护任何类型的数据对象
      • 8字节 (x86) or 16字节 (x86-64) 对齐在 Linux 上框
    • 只能操作或改变空闲块
    • 一旦块被分配,就不允许修改或移动它了
      • 比如, 压缩已分配块的技术是不允许使用的

性能指标:吞吐量

  • 假定 n 个分配和释放请求的某种序列:
    • R0, R1, …, Rk, … , Rn-1
  • 目标: 最大化吞吐量,最大化内存利用率
    • 这些目标经常是互相矛盾的
  • 吞吐量Throughput:
    • 每个单位时间内完成的请求数
    • 例如:
      • 10秒内完成5,000个分配请求和5,000个释放请求
      • 吞吐量是 1,000 次操作/秒

动态内存分配实现
对于处理碎片和分配内存,有这些问题需要处理:

  • 我们如何知道一个指针可以释放多少内存?
  • 我们如何记录空闲块(区分空闲与分配)?
  • 将一个新分配的块放置到某个比较大的空闲块后,我们如何处理这个空闲块中的剩余部分?
  • 我们如何选择一个空闲块去分配 – 很多都合适呢?
  • 我们如何处理一个刚刚被释放的块—前后也有空闲呢?
    标准方法(standard method):我们如何知道释放多少
  • 在块的前面的 word 中记录该块的长度.
    • 这个 word 被称为头部
  • 每个被分配块都需要一个这样的“word”

    如上图,malloc(4) 分配的有效载荷长度,我们还需要给这一个区域编码为一个 内存块,也就是实际上要开五个格子,拿一个格子出来放头信息