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

103 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记

1.自动内存管理/垃圾回收

三个任务

  1. 为新对象分配内存
  2. 找到存活的对象
  3. 回收死亡对象的内存空间 相关概念(GC也就是Collector,垃圾回收器)
  4. 业务线程,也是用户线程
  5. GC线程,负责垃圾回收的线程
  6. Serial GC:只有一个GC线程,GC线程和用户线程串行化执行
  7. Parallel GC:多个GC线程同时回收垃圾
  8. Concurrent GC:用户线程和GC线程可以同时执行。这要求GC线程能够感知对象指向关系的变化。

image.png

GC算法的评价指标

  1. 安全性:不能够回收存活的对象
  2. 吞吐率:1-GC时间/程序执行总时间
  3. 暂停时间:STW,Stop The World
  4. 内存开销:GC元数据开销

追踪垃圾回收

  1. 对象被回收的条件:指针指向关系不可达的对象
  2. 标记根对象。包括静态变量,全局变量,常量,线程栈
  3. 找到可达对象。求指针传递关系的闭包,从根对象开始,找到所有可达的对象
  4. 清除不可达的对象。
    1. 将存活的对象复制到另外的内存空间。Copying GC,复制到另外的内存
    2. 将死亡对象的内存标记为可用。Mark-sweep GC,标记清除
    3. 移动并整理存活对象。Mark-compact GC,标记后,原地整理

分代GC

  1. 分代假说,大多数的对象都die young。很多对象刚分配就不使用了
  2. 每个对象的年龄:经历GC的次数
  3. 目的:对年轻和老年的对象,采用不同的GC策略,降低内存管理整体开销
  4. 不同年龄的对象放在不同heap区域
  5. 年轻代:常规的对象分配,存活对象较少,可以采用copying GC,GC吞吐率高
  6. 老年代:对象趋于一致存活,反复复制的话开销比较大,可以采用mark-sweep GC 引用计数
  7. 每个对象都有一个与之相关联的计数,记录了引用该对象的指针或者引用数目
  8. 对象存活条件:引用计数大于0
  9. 优点:内存管理操作被平摊在程序执行过程中,不需要了解runtime细节,比如c++的智能指针。
  10. 缺点:维护引用计数开销大,要通过原子操作保证对引用计数的原子性和可见性。循环引用问题,但是可以通过弱引用解决。内存开销,需要引入额外的空间存储引用数目。回收内存依然可能引发暂停。

2. 内存分配

分块

  1. 调用系统条用mmap()从os获取一块内存,例如4MB
  2. 先将内存分成大块,mspan,8kb
  3. mspan一共有67类,每一类mspan能够存储的对象大小不同。
  4. noscan mspan,GC不需要扫描的mspan,分配的对象不包含指针
  5. scan mspan,GC需要扫描的mspan,分配的对象包含指针

缓存

  1. 每个P包含一个mcache用于为P上绑定的G快速分配内存
  2. mcache管理67*2个mspan链表,每个类型的mspan有两个链表,scan和noscan
  3. 当mcache中内存不够用时,向mcentral申请mspan。
  4. mcentral管理一类mspan。也就是说一共67*2个mcentral,归mheap管理。

go中内存分配优化

  1. 内存分配比较耗时,分配路径长
  2. 小对象占比高
  3. 对象分配是非常高频的操作,每秒分配GB级别

3. 编译

词法分析,语法分析,语义分析,中间表示,代码优化,代码生成

4. 内联函数

将被调用函数在调用位置展开,同时重写代码以反应参数的绑定 优点

  1. 消除函数调用开销,比如参数传递,保存寄存器
  2. 将过程间分析转换为过程内分析,帮助其他优化,比如逃逸分析。 缺点
  3. 函数体变大,指令缓存不友好
  4. 编译生成的go镜像变大

5. 逃逸分析

  1. 分析代码中指针的动态作用域:指针在何处被访问
  2. 思路
    1. 从对象分配处出发,沿着控制流,观察对象的数据流
    2. 若返现指针p在当前作用于s:
  •     作为函数传递给其他变量
    
  •     传递给全局变量
    
  •     传递给其他携程
    
  •     传递个已逃逸的指针指向的对象时
    
    3. 则指针p逃逸出s
  1. 优化。未逃逸的指针在栈上分配,而栈上分配和回收非常快,移动栈顶指针即可,还减少了GC的负担。可以使用内联函数优化。