这是我参与「第三届青训营 -后端场」笔记创作活动的的第二篇笔记。
性能优化
- 业务层优化
- 语言运行时优化 解决更通用的问题
- 数据驱动 利用pprof
自动内存管理(垃圾回收)
相关概念
- 动态内存:程序在运行时根据需求动态分配的内存
- 自动内存管理:由程序语言的运行时系统管理动态内存,解决double-free,use-after-free等等问题
- 三个任务:为新对象分配空间,找到存活对象,回收死亡对象的内存空间。
- Mutator: 业务线程,分配新对象,修改对象指向关系。
- Collector: GC线程,找到存活对象,回收死亡对象的内存空间。
- Serial GC: GC算法中只有一个collector.(会暂停,即stop the world)
- Parallel GC: 支持多个collector同时回收的GC算法。(会暂停)
- Concurrent GC: mutator(s)和collector(s)可以同时执行。collector需要感知到对象指向关系的变化,当已标记的对象指向未标记的对象时需要把未标记的对象标记。
- 吞吐率:1-GC时间/程序执行总时间
追踪垃圾回收
1. 标记根对象 (静态变量,全局变量,常量,线程栈等)
2. 标记:找到可达对象 从根对象出发,找到所有可达对象
3. 清理: 所有不可达对象
- Coping GC: 将存活对象复制到另外的内存空间
- Mark-sweep GC: 将死亡对象的内存标记为可分配
- Mark-compact GC: 移动并整理存活对象(原地整理对象)
思想:根据对象的生命周期选择不同的标记和清理策略
分代GC (Generational GC)
- 分代假说: most objects die young(很多对象分配出来后就不再使用了)
- 每个对象都有年龄:经历GC的次数
- 目的: 针对不同年龄的对象制定不同的GC策略,降低整体内存管理的开销
- 不同年龄的对象处于heap的不同区域
- 年轻代:存活对象较少,可以使用Coping-GC,GC吞吐率很高
- 老年代:对象趋向于一直存活,反复复制开销较大,可使用Mark-sweep GC
引用计数
- 每个对象都有一个与之关联的引用数目
- 对象存活条件:当且仅当引用数目大于0
- 优点:1.内存管理的操作被平摊到程序执行中。 2.内存管理不需要里了解runtime的细节。
- 缺点:1. 维护引用计数的开销大,要通过原子操作来保证引用计数的原子性和可见性。2.无法回收环形数据结构 3. 额外的内存开销 4. 回收内存仍然可能引发暂停
GO内存管理及优化
go内存管理
- 分块 提前将内存分块,根据对象大小分配合适的块返回
- 缓存 每一个p包含一个mcache用于快速分配,用于为绑定在p上的g分配对象,mcache管理一组mspan,mspan没有分配的对象时会缓存在mcentrol,而不是立刻释放并归还给OS
go内存管理优化
- 对象分配非常高频,每秒分配GB级的内存
- 小对象占比较高
- go 内存分配比较耗时,分配路径长:g->m->p->mcache->mspan->memory block->return pointer
- 优化方案:banlanced gc 本质为将多个小对象的分配合并成一个大对象的分配