这是我参与「第五届青训营 」笔记创作活动的第6天。
Go语言自动内存管理机制: malloc()动态内存,程序运行自动回收内存。有几个常用概念:
- Mutator:业务线程,分配新对象,修改对象指向关系
- Collector:GC线程,找到存活,回收死亡
- Serial GC:只有一个collector
- Parallel GC:多个collector并行
- Concurrent GC:Mutator和collector交替进行
追踪垃圾回收:找到满足条件————指针指向关系不可达的对象。一般标记根对象后,从根对象出发逐一寻找。清除不可达对象有三种方法:Copying GC,Mark-Sweep GC,Mark-compact GC。策略选择的判准:根据对象的生命周期!
- Copying GC: 将对象复制到另外的内存空间
- Mark-Sweep GC:使用free-list对于空闲内存管理
- Mark-Compact GC:原地整理对象
为了对于分配对象的生命周期妥当管理:增加分代GC,“年轻”,“年老”策略不同。 位于heap不同区域:
- 年轻GC存活很少,用Copying GC
- 年老GC倾向于一直存活,Copying开销大,用Mark-Sweep GC
另一种常用管理方法:每个对象维护一个引用计数(代表关联对象数),对象存活条件为引用计数>0。
优点:内存管理被平摊,C++智能指针可以不用了解runtime执行细节;缺点:维护开销大(原子操作),环形数据结构难以处理,内存开销,引发暂停等。
Go内存管理的优化:
分块:提前将内存分块(mmap->mspan->小块分配),分配内存时根据对象大小选择合适的块并返回。每个p含有一个mcache用于分配,mcache管理一组mspan,需要分配时请求mcentral,没有分配时mcache缓存在mcentral中
基于此的方案:Balanced GC,即每个g(goroutine)绑定一大块内存,称作goroutine allocation buffer(GAB),类似于栈数据结构,维护三个指针base,end,top,分配时在GAB的top处增长。与其他内存分配请求不冲突。(本质为小内存分配合并为大内存分配,用Copying GC管理小对象)
为便于编译器优化,可进行编译器静态分析。Go编译器优化有这些内容:
- Inlining(内联)函数体副本直接替换到调用位置上
- Beast Mode 增大边界,使更多函数能被内联。常用的分析方法:逃逸分析。
总而言之,内存管理对于任意一种语言的性能优化都极其要,掌握清理策略,分配策略,分析策略等,有助于之后开发过程中的性能优化。