Go语言之内存管理| 青训营笔记

101 阅读4分钟

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

前言

本文为作者参与青训营学习的课程总结,记录着我对课程内容的收获和思考,以便用于日后的复习查阅。如果所写内容出现不准确的表述或错误,欢迎各位在评论区友好指出。

1.自动内存管理

自动内存管理其实指的是系统的垃圾回收(Garbage Collection):由程序语言运行时系统回收动态内存。

交由系统回收的好处是:

  • 避免手动内存管理,能够专注与实现业务逻辑
  • 保证内存的使用的正确性和安全性,避免出现如内存重复释放问题(double-free problem),内存释放后使用问题(use-after-free problem)

GC的主要三个任务:

  • 为新对象分配空间
  • 找到存活对象
  • 回收死亡对象的内存空间

相关概念

  • Mutator:业务线程,分配新对象,修改对象的指向关系
  • Collector:GC线程,找到存活对象,回收死亡对象的内存空间
  • Serial GC:串行GC,只有一个collector。这种回收方式在存在多个mutator时会暂停它们的线程,然后由一个collector进行回收
  • Parallel GC:并行GC,和串行类似,支持多个collectors同时回收的GC算法
  • Concurrent GC:mutator(s)和collector(s)可以同时执行

image.png

评价GC算法

我们可以通过以下几个方面来评价GC算法的好坏程度:

  • 安全性(Safety):不应回收存活的对象,这是最基本的要求

  • 吞吐率(Throughput):除开GC消耗时间后的运行时间与程序执行总时间的占比 ,这个占比越大越好

    1GC时间程序执行总时间1- \frac{GC时间}{程序执行总时间}
  • 暂停时间(Pause time):业务线程因为GC导致暂停的花费时间,这个时间越短越好

  • 内存开销(Space overhead):GC的元数据开销

追踪垃圾回收

通过根对象的引用链来标记可达对象使其不被回收,将其余不可达的对象视为垃圾进行回收清理。

常见的回收不可达对象有以下几种方法:

  • Copy GC:将存活对象复制到另外的内存空间,以便腾出一块连续的空闲内存
  • Mark-sweep GC:将死亡对象的内存标记为“可分配”(free),可以将需要内存的对象分配到free上
  • Mark-compact GC:移动并整理存活对象,将存活对象移动到一起,使得可以后面内存分配时可以紧接着前面的已分配内存地址。

分代GC

每个对象都可以根据其经历过的GC次数来划定其年龄,针对年轻和老年的对象指定不太的GC策略,以降低整体内存管理的开销。

  • 年轻代:由于年轻代中存活对象很少,可以采用copying GC
  • 老年代:对象趋于一直存活,反复进行复制开销较大,因此可以采用mark-sweep GC

引用计数

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

优点:内存管理的操作被平摊到程序执行的过程中(在新建或销毁对象时对应的引用数就会修改),并且内存管理不需要了解 runtime 的实现细节。

缺点:由于要维护每个对象的引用计数,所以维护开销较大,因为需要原子操作来保证对计数操作的原子性和可见性。另外也无法回收环形的数据结构。

2.内存分配

分块

目标:为对象在堆上分配内存
分配步骤:

①提前将内存分块:

  • 调用mmap()向操作系统申请一大块内存,然后不断将大内存块分配成大块(mspan)
  • mspan分为noscan mspan:分配不包含指针的对象(GC不需要扫描)和scan mspan:分配包含指针的对象(GC需要扫描)
  • 再将mspan继续划分成特定大小的小块,用于对象分配

②对象分配:根据对象的大小,选择最合适的块返回

缓存

使用mcache管理一组mspan,用于快速分配内存。

当mcache管理的mspan里没有分配的对象时,mspan会被缓存在mcentral中。

当mspan分配完毕,macache会向mcentral申请带有未分配块的mspan。