自动内存管理&内存管理与优化 | 青训营笔记

270 阅读4分钟

自动内存管理&内存管理与优化 | 青训营笔记

这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天

自动内存管理

概念

  • 动态内存

    • 程序在运行时根据需求动态分配的内存:malloc()
  • 自动内存管理(垃圾回收)

    • 由程序语言的运行时系统回收动态内存
    • 避免手动内存管理,专注于实现业务逻辑
    • 保证内存使用的正确性和安全性
  • 三个任务

    • 为新对象分配空间
    • 找到存活对象
    • 回收死亡对象的内存空间
  • Mutator:业务线程,分配新对象,修改对象指向关系

  • Collector:GC线程,找到存活对象,回收死亡对象的内存空间

  • Serial GC:只有一个collector。执行时业务线程必须暂停。

  • Parallel GC:支持多个collectors同时回收的GC算法。执行时业务线程必须暂停。

  • Concurent GC:mutator(s)和collector(s)可以同时执行

    • 前提:Collectors必须感知对象指向关系的改变,例如已标记对象指向的对象必须被标记。

评价GC算法优劣的指标:

  • 安全性:不能回收存活的对象基本要求
  • 吞吐率:1GC时间程序执行总时间1-\frac{GC时间}{程序执行总时间} 花在GC上的时间
  • 暂停时间:业务是否感知
  • 内存开销:GC元数据开销

GC算法

分代GC概念

  • 分代假说,很多对象在分配出来后很快就不再使用了
  • 每个对象都有年龄:经历过GC的次数
  • 目的:对年轻和老年的对象,指定不同的GC策略,降低整体内存管理的开销
  • 不同年龄的对象处于heap的不同区域

追踪垃圾回收

对象被回收的条件:指针指向关系不可达的对象。

感觉有点像并查集,又有点像floyd求传递闭包。

  1. 标记根对象
    1. 静态变量、全局变量、常量、线程栈等
  2. 从根对象,标记所有可达的对象(求一个传递闭包)
  3. 清理所有不可达的对象
    1. 将存活对象复制到另外的内存空间(Copying GC)
    2. 或者将死亡对象的内存标记为可分配(Mark-sweep GC)
    3. 或者移动并整理存活对象(有点像OS的那个清理碎片的感觉,把小碎片集合在一起变成大碎片)(Mark-compact GC)

根据对象的声明周期,使用不同的标记和清理策略

  • 年轻代
    • 常规的对象分配
    • 由于存活对象很少,可以采用copying colliction
    • GC吞吐率很高
  • 老年代
    • 对象倾向于一直活着,反复复制开销较大
    • 可以采用mark-sweep collection

引用计数

每个对象都有一个与之关联的引用数目,对象存活的条件为当且仅当引用数大于0。

听着就是拓扑排序!只不过拓扑排序是有方向的。

优点:

  • 内存管理的操作被平摊到程序执行过程中
  • 内存管理不需要了解runtime的实现细节

缺点:

  • 维护引用计数的开销较大;通过原子操作保证对引用计数操作的原子性和可见性
  • 无法回收环形数据结构(和拓扑一样)
  • 内存额外开销,每个对象都要引入额外的内存空间来存储引用数目
  • 回收内存时间依然可能引发暂停

内存管理及优化

内存分配

分块

目标:为对象在heap上分配内存。

提前将内存分块:

  • 调用系统调用mmap()向OS申请一大块内存,例如4MB
  • 先将内存划分成大块,例如8KB,称作mspan
  • 再将大块继续划分成特定大小的小块,用于对象分配
  • noscan mspan,分配不包含指针的对象,也就是GC不需要扫描
  • scan mspan,分配包含指针的对象,GC需要扫描

对象分配的原则是根据对象的大小,选择最合适的块返回

缓存

原理好像是TCMalloc,thread caching。

  • 每个p包含一个mcache用于快速分配和绑定与p上的g分配对象
  • mcache管理一组mspan
  • 当mcache中的mspan分配完毕,向mcenteral申请带有未分配块的mspan
  • 当mspan中没有分配的对象,mspan会被缓存在mcentral中,而不是立刻释放并归还给OS

内存管理优化

内存管理优化的重要性:

  • 对象分配是非常高频的操作:每秒分配GB级别的内存
  • 小对象占比较高
  • Go内存分配比较耗时

一种可行的优化方案:Balanced GC

  • 每个g都绑定一大块内存(1KB),称作goroutine allocation buffer(GAB)
  • GAB用于noscan类型的小对象分配:<128B
  • 使用三个指针维护GAB:base, end, top
  • 分配简单高效
if top + size <= end {
    addr := top
    top += size
    return addr
}