这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记.
0x0 Pre
在本文章中会了解到:
- 垃圾回收(重要)
- Go 垃圾回收(了解)
- Go 编译器的知识(了解)
很多内容和 Java GC 很类似。
0x1 自动内存管理(GC)
自动内存管理也叫垃圾回收,由程序语言运行时系统管理动态内存。
- 避免手动内存管理,专注于实现业务逻辑
- 保存内存使用的正确性和安全性:比如 double-free 和 use after free 的问题 相关概念:
- Mutator: 业务线程,分配新对象,修改对象的指向关系
- Collector:GC线程,找到存活对象,回收死亡对象
- Serial GC:只有一个 collector
- Parallel GC:支持多个 collectors 同时回收的 GC 算法
- Concurrent GC:mutators 和 collectors 可以同时执行
GC 算法的指标:
- 安全性:不能回收存活的对象
- 吞吐量:花在GC上的时间
- 暂停时间:业务是否能感知内存回收,STW
- 内存开销
常见垃圾回收算法:追踪垃圾回收(Tracing garbage collection)和引用计数法(Reference Counting)
追踪垃圾回收
追踪垃圾回收算法大致分为三步:
- 标记根对象:静态常量,全局变量,常量,线程栈等
- 标记可达对象:从根对象触发,找到所有可达对象
- 清理不可达对象:清理不可达对象有着不同的策略(和 JVM 垃圾回收类似),具体回收策略要根据对象的声明周期来确定
- Copying GC:将存活对象复制到另外的内存空间,类似 JVM 中的幸存区 From 和 to。
- Mark-Sweep GC:即标记清楚法,将死亡对象的内存标记为“可分配”
- Mark-Compact GC:移动并且整理存活对象,原地整理对象
这里的具体回收策略使用了分代 GC:
年轻代中的对象存活时间较短,可以采用 copying GC,老年代中存活时间较长,可以采用Mark-Sweep GC。
引用计数法
每个对象都有一个与之关联的引用数目,当引用数目大于0的时候对象存活。 优点:
- 内存管理的操作被平摊到程序执行过程中
- 内存管理不需要了解 runtime 的实现细节(比如 C++ 中的智能指针) 缺点:
- 维护引用计数的开销较大:需要通过原子操作保证对引用计数操作的原子性和可见性
- 无法回收环形数据结构————weak reference
- 内存开销:每个对象都引入额外的内存空间存储引用数目
0x2 Go内存管理及优化(Go GC)
Go 内存分配
-
分块 目标:为对象在堆上分配内存。
提前将内存分块:
- 首先系统调用
mmap()向 OS 申请分配一块内存空间,比如4MB - 将获得的内存划分为大块,例如 8KB,称为mspan, noscan mspan 用于分配不包含指针的对象,scan mspan 分配包含指针的对象。GC 只会扫描scan mspan
- 再将大块继续划分为特定大小的小块,用作对象分配
- 首先系统调用
-
缓存
Go 内存缓存借鉴了 TCMalloc(Thread Caching) 的技术加快整体内存分配速度。
字节跳动的针对小对象分配的优化方案:Balanced GC,并且取得了不错的效果 。
0x3 编译器和静态分析
静态分析分为数据流和控制流分析,也分为过程间分析和过程中分析。
0x4 Go 编译器优化
函数内联