掘金 阅读 ( ) • 2023-01-31 08:46

theme: cyanosis highlight: github

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

前言

本文主要内容为Go语言自动内存管理的基本概念、基本算法以及垃圾回收机制。

背景

自动内存管理主要管理的是动态内存动态内存指的是程序在运行时根据需求动态分配的内存,比如C语言中的malloc()函数分配的内存。

自动内存管理也称为垃圾回收,主要目的是由程序语言的运行时系统管理动态内存,这样做有以下两方面的好处:

  • 避免手动内存管理,专注于实现业务逻辑
  • 保证内存使用的正确性和安全性,比如C语言中的内存多次释放:double-free problem, 释放后再次使用use-after-free problem。

由此可见,手动释放内存存在很多问题,如果使用不当的话,可能会引起程序的崩溃、漏洞,而自动内存管理可以帮我们自动处理这些问题。

自动内存管理有3个主要的核心任务:

  1. 为新对象分配空间
  2. 找到存活对象
  3. 回收死亡对象的内存空间

相关概念

下面这张图是Go对自定内存管理的一段介绍,我们先从这段介绍中了解下一些相关的基本概念。

image.png

  • Mutator:业务线程,分配新对象,修改对象指向关系
  • Collector:GC线程,找到存活对象,回收死亡对象的内存空间
  • Serial GC:只有一个collector,会暂停(STW)
  • Parallec GC:支持多个collectors同时回收GC算法,会暂停(STW)
  • Concurrent GCmutator(s)collector(s)可以同时执行,Collectors必须感知对象指向关系的改变。

image.png

Concurrent GC进行内存回收时,业务线程和GC线程是同时进行的,所以存在着一定挑战难度,这个挑战主要是GC线程必须感知对象指向关系的改变。

image.png

评价GC算法

GC算法的好坏由以下几点来进行评价:

  • 安全性(Safety):不能回收存活的对象,这也是基本要求
  • 吞吐率(Throughput):$$1 - \frac{GC时间}{程序执行总时间}$$,也就是花在GC上的时间
  • 暂停时间(Pause time):stop the world(STW)业务是否感知
  • 内存开销(Space overhead):GC元数据开销,一般是开销越小越好

GC算法

接下来将介绍两种常见的GC相关的技术:

  • 追踪垃圾回收(Tracing garbage collection)
  • 引用计数(Reference counting)

追踪垃圾回收

追踪垃圾回收,当一个对象的指针指向关系不可达的时候,该对象就要被回收了。

追踪垃圾回收算法垃圾回收步骤:

  1. 标记根对象
    • 标记包括 静态变量、全局变量、常量、线程栈等
  2. 标记:找到可达对象
    • 求指针指向关系的传递闭包:从根对象触发,找到所有可达对象
  3. 清理:所有不可达对象
    • 将存活对象复制到另外的内存空间(Copying GC)
    • 将死亡对象的内存标记为”可分配“(Marking-sweep GC)
    • 移动并整理存活对象(Mark-compact GC)

清理策略有很多种,在实际清理的时候应该根据对象的生命周期,使用不同的标记和清理策略。

image.png

分代 GC

分代GC(Generational GC)是一种常见的内存管理方式,思想是基于分代假说(Generational hypothesis)——大多数对象很快就死掉了(most objects die young),很多对象在分配出来后很快就不再使用了。

每个对象都有年龄,也就是对象经历过GC的次数,比如经历了2次GC那么他的年龄就为2。

分代GC根据对象年龄的不同,把对象放在不同的区域,年轻代对象放在Young Generation区域,老年代放在Old Generation区域,这样做的目的为对年轻和老年的对象,制定不同的GC策略,降低整体内存管理的开销。

年轻代(Young Generation)为常规的对象分配,由于存活对象很少,可以采用copying collection 算法,GC吞吐量很高。

老年代(Old Generation)内对象趋向于一直活着,反复复制开销较大,可以采用mark-sweep collection算法。

引用计数

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

引用计数管理内存的优点如下:

  • 内存管理的操作被平摊到程序执行过程中
  • 内存管理不需要了解runtime的实现细节,有一些库可以帮助实现引用计数,比如C++智能指针(smart pointer)。

当然引用计数也有缺点:

  • 维护引用计数的开销较大:通过原子操作保证对引用计数操作的原子性和可见性
  • 无法回收环形数据结构——weak reference
  • 内存开销:每个对象都引入的额外内存空间存储引用数目
  • 回收内存时可能引发暂停——大的数据结构

总结

本文介绍了自动内存管理的背景意义以及几种自动内存管理算法,自动内存管理算法能让我们关注于业务开发,减少手动管理内存可能出现的问题,学术界和工业界在一直致力于解决自动管理技术的不足之处。

引用

性能优化及自动内存管理