这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
自动内存管理
malloc() 分配的内存
优点
- 避免手动管理,专注于业务逻辑
- 保证内存使用的正确性和安全性
相关概念
- Mutator:业务线程,分配新对象,修改对象指向关系
- Collector: GC线程,找到存活对象,回收死亡对象的内存空间
- Serial GC:只有一个collector
- Parallel GC:支持多个collectors同时回收的GC算法
- Concurrent GC: mutator(s)和collector(s)可以同时执行
- Collectors必须感知对象指向关系的改变!
评价方法
- 安全性(Safety):不能回收存活的对象基本要求
- 吞吐率(Throughput): 1- GC时间 / 程序执行总时间 花在GC上的时间
- 暂停时间(Pause time): stop the world(STW) 业务是否感知
- 内存开销(Space overhead) GC元数据开销
三个任务
- 为新对象分配空间
- 找到存活对象
- 回收死亡对象的内存空间
追踪垃圾回收
- 求指针关系的传递闭包:从根对象出发,找到所有可达对象
- 清理方法
- 将存活对象复制到另外的内存空间(Copying Gc)
- 将死亡对象的内存标记为“可分配”(Mark-sweep Gc)
- 移动并整理存活对象(Mark-compact GC)
分代GC
- 针对年轻和年老的对象,指定不同的GC策略
- 年轻
- 存活对象很少
- Copying Gc
- 吞吐率很高
- 老年
- Mark-sweep Gc
- 年轻
引用计数
-
每个对象关联一个引用数目
-
优点
- 内存管理的操作被平摊到程序执行过程中
- 内存管理不需要了解runtime的实现细节:C++智能指针(smart pointer)
-
缺点
- 维护引用计数的开销较大:通过原子操作保证对引用计数操作的原子性和可见性
- 无法回收环形数据结构——weak reference
- 内存开销:每个对象都引入的额外内存空间存储引用数目
- 回收内存时依然可能引发暂停
内存管理优化
- 小对象占比较高
- Balance GC
- 正常分配路径 g -> m -> p -> mcache -> mspan -> memory block -> return pointer
- 优化:在g上绑定一个大内存GAB(1kb),使用指针碰撞风格分配(base,top,end),用于noscan类型的小对象分配
- 问题:内存延迟释放
- 设定阈值,超过阈值,将存活对象复制到另一个GAB中
- copying GC
Go 编译器优化
函数内联
- 优点
- 消除函数调用开销,例如传递参数、保存寄存器等
- 将过程间分析转化为过程内分析,帮助其他优化,例如逃逸分析
- 缺点
- 函数体变大,instruction cache (icache)不友好
- 编译生成的Go镜像变大
- 大多数都是正向优化
- Go函数内联受到的限制较多
- 逃逸分析
- 分析指针的动态作用域,因为Go是允许在函数外访问变量的
- 未逃逸的对象可以在栈上分配