这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记。
1、自动内存管理
- 动态内存:程序在运行时根据需求动态分配的内存:malloc()
- 自动内存管理:由程序语言的运行时系统管理动态内存,避免手动管理内存,从而专注于实现业务逻辑;保证内存使用的正确性和安全性。
- 三个任务:为新对象分配空间;找到存活对象;回收死亡对象的内存空间。
1、1 概念
-
Mutator:业务线程,分配新对象,修改对象指向关系。
-
Collector:GC线程,找到存活对象,回收死亡对象的内存空间。
-
Serial GC:只有一个collector
-
Parallel GC:支持多个collector同时回收的GC算法
- Concurrent GC:mutator和collector同时执行
注:collector必须感知对象指向关系的改变。
1、2 追踪垃圾回收
- 对象被回收的条件:指针指向关系不可达的对象。
- 标记根对象:如静态变量、全局变量、常量、线程栈等
- 找到可达对象:求指针指向关系的传递闭包,从根对象出发,找到所有可达对象
- 清理所有不可达对象:三种算法:Copying GC | Mark-sweep GC | Mark-compact GC
1、3 分代GC
对新生代和老年代指定不同的策略,降低整体内存管理的开销。
- 新生代:常规的对象分配,由于存活对象少,采用copying GC算法。
- 老年代:对象一直趋近于活着,反复复制开销大,采用mark-sweep GC算法
1、4 引用计数
对象存活的条件:当且仅当引用数大于0。
无法回收环形数据结构。 因为无法确定其起始位置和结束位置。
2、内存分配
内存分块:
- noscan mspan:GC不需要扫描
- scan mspan:GC需要扫描 缓存: mcache管理一组mspan
3、内存逃逸
一个对象本应该分配在栈上面,结果分配在了堆上面,这就是内存逃逸。
Golang 中的变量只要被引用就一直会存活,存储在堆上还是栈上由内部实现决定而和具体的语法没有关系。知道变量的存储位置确实和效率编程有关系。如果可能,Golang 编译器会将函数的局部变量分配到函数栈帧(stack frame)上, 然而,如果编译器不能确保变量在函数 return之后不再被引用,编译器就会将变量分配到堆上。而且,如果一个局部变量非常大,那么它也应该被分配到堆上而不是栈上。逃逸分析的好处是为了减少gc的压力。