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

61 阅读4分钟

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

1 自动内存管理

1.1 相关概念

  • Mutator:业务线程,分配新对象,修改对象指向关系

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

  • Serial GC:只有一个collector

  • Parallel GC:支持多个 collection 同时回收的GC算法

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

注意:Collectors必须感知对象指向关系的改变!

1.2 GC算法评价指标

  • 安全性(Safety):不能回收存活的对象是基本要求
  • 吞吐率(Throughput):花在GC上的时间 1GC时间/程序执行总时间1- GC时间/程序执行总时间
  • 暂停时间(Pause time):stop world(STW)业务是否感知
  • 内存开销(Space overhead):GC 元数据开销

1.3 GC算法

1.3.1 追踪垃圾回收

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

1.3.2 ## 分代GC(Generational GC)

分代GC是基于这样一个假设:大部分新分配的对象存活周期较短,在分配后的第一轮GC中就会被回收掉。

如果这个假设成立,那么GC期间只去扫描和清扫新分配的对象就可以清扫掉大部分需要回收的对象,这样就可以节省GC的时间。

几个概念:

新生代对象:上一轮GC后新分配的对象。

老年代对象:上一轮GC中存活的对象。

Minor GC:小型GC,只对新生代对象进行清扫。

Major GC:大型GC,清扫所有对象,因为一直进行Minor GC的话,会造成程序占用的内存越来越多,老年代对象越来越多,所以在分代GC中要穿插使用Minor GC和Major GC,Major GC可以是标记-清扫GC算法或复制GC算法,一般是和复制GC算法配合使用(容易区分新生代对象和老年代对象)。

1.3.3 引用计数(Reference counting)

GC标记-清除算法是在没有可用分块时,会调用GC函数,进行垃圾处理回收,即是一种无侵入式的GC,这样就造成,最大暂停时间较大,此外,标记-清除算法即是产生了垃圾,也不会将其马上回收,只会在没有分块的时候,将垃圾一并回收;GC引用计数法是一种侵入式算法,其最大暂停时间表现较好,比较均匀,内存管理和mutator同时运行是引用计数法的一大特征。

引用计数法具有以下优点:

  1. 可以立即回收垃圾。因为每个对象在被引用次数为0的时候,是立即就可以知道的。
  2. 没有暂停时间。这个很容易理解,对象的回收根本不需要另外的GC线程专门去做,业务线程自己就搞定了。所以不需要stop the world,当然,在多线程的情况下,必要的同步和互斥操作还是需要的。

缺点:

  1. 在每次赋值操作的时候都要做相当大的计算,尤其这里面还有递归调用。这是比较麻烦的。
  2. 一个致命缺陷是循环引用,就是, objA引用了objB,objB也引用了objA,但是除此之外,再没有其他的地方引用这两个对象了,这两个对象的引用计数就都是1。这种情况下,这两个对象是不能被回收的。

2 Go内存优化

2.1 Go内存分配

2.1.1 分块

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

  • 调用系统调用mmap()向OS申请一大块内存,例如4MB

  • 先将内存划分成大块,例如8 kb,称作对象分配mspan

  • 再将大块继续划分程特定大小的小块,用于对象分配

  • noscan mspan:分配不包含指针的对象——GC不需要扫描

  • scan mspan:分配包含指针的对象——GC需要扫描

2.1.2 缓存

Go内存管理构成了多级缓存机制,从OS分配得的内存被内存管理回收后,也不会立刻归还给OS,而是在Go runtime内部先缓存起来,从而避免频繁向OS申请内存。

  • TCMalloc: thread caching

  • 每个p包含一个mcache用于快速分配,用于为绑定于p上的g 分配对象

  • mcache管理一组mspan

  • 当mcache 中的 mspan 分配完毕,向mcentral申请带有未分配块的mspan

  • 当mspan中没有分配的对象,mspan 会被缓存在mcentral中,而不是立刻释放并归还给oS

2.2 内存优化

1、对象分配时非常高频的操作:每秒分配GB级别的内存

2、小对象占比比较高

2.2.1 Balanced GC

  • 每个g都绑定一大块内存(1kb),称作goroutine allocation buffer (GAB)

  • GAB用于noscan类型的小对象分配: <128B

  • 使用三个指针维护GAB: base,end,top