这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天
本次笔记将记录go的垃圾回收机制以及内存管理机制
自动内存管理(垃圾回收)
动态内存
程序在运行时根据需求动态分配的内存:ma11oc()
自动内存管理(垃圾回收)
由程序语言的运行时系统管理动态内存
· 避免手动内存管理,专注于实现业务逻辑
· 保证内存使用的正确性和安全性:double-free problem,use-after--free problem
自动内存管理的三个任务:
- 为新对象分配空间
- 找到存活对象
- 回收死亡对象的内存空间
几个概念
Mutator : 业务线程,分配新对象,修改对象指向关系
Collector : GC线程,找到存活对象,回收死亡对象的内存空间
Serial GC : 只有一个collector
Parallel GC : 支持多个collectors同时回收的GC算法
Concurrent GC : mutator(s)和collector(s)可以同时执行
评价GC算法:
安全性(Safety) : 不能回收存活的对象基本要求
吞吐率(Throughput) : 1-GC时间程序执行/总时间程序执行总时间GC时间,gc时间越小越好
暂停时间(Pause time:stop the world(STW) : 业务是否感知,暂停时间越小越好
内存开销(Space overhead) : GC元数据开销,开销越小越好
一、追踪垃圾回收
1、追踪垃圾回收及清理的三个机制(怎么清理)
追踪垃圾回收的几个概念
对象被回收的条件:指针指向关系不可达的对象。
首先标记根对象:静态变量、全局变量、常量、线程栈等;
然后从根对象出发,找到所有可达对象并标记;
最后清理不可达的“死亡”对象。
垃圾清理机制一:Copying GC
将存活对象复制到另外的内存空间
将已标记的对象copy到一个新的内存空间,随后gc将原来的所有内存清理掉
垃圾清理机制二:Mark-sweep GC
将死亡对象的内存标记为“可分配”
标记的存活对象不动,将不可达的“死亡”对象变为可分配状态
垃圾清理机制三:Mark-compact GO
移动并整理存活对象
将标记的存活对象移动刀内存起始位置,并清理掉后面的内存
2、垃圾回收策略:Generational GC(什么时候用什么方法清理)
Generational GC中译为分代GC
顾名思义,分代GC就是指将内存里的对象分成两代:年轻代、老年代。不同年龄的对象处于堆的不同区域
年轻代:由于存活对象很少,可以采用copying collection GC,吞吐率很高
老年代:象趋向于一直活着,采用copying collection GC反复复制开销较大,可以采用mark-sweep collection
二、Reference counting(引用计数)
每个对象都有一个与之关联的引用数目。
对象存活的条件:当且仅当引用数大于0。
如上图,某对象被几个箭头所指,其计数就为几,清除所有计数为0的对象,计数是实时更新的。
优点:
·内存管理的操作被平难到程序执行过程中
·内存管理不需要了解runtime的实现细节
缺点:
·维护引用计数的开销较大:通过原子操作保证对引用计数操作的原子性和可见性
·无法回收环形数据结构一weak reference
·内存开销:每个对象都引入的额外内存空间存储引用数目
·回收内存时依然可能弱引发暂停
内存管理机制及字节的优化
Go内存分配——分块
- 在哪分配:在heap上给对象分配内存
- 如何分配:提前将内存分块(从大到小),系统调用mmap()向OS申请一大块内存,例如4MB。再将内存划分成多个大块,例如多个8KB,每个称作mspan。每个mspan再继续划分成特定大小的小块,用于按需对象的分配分块 如下图所示
Go内存分配——缓存
如上图
- TCMalloc:thread caching
- 每个p包含一个mcache用于快速分配,用于为绑定于p上的g分配对象
- mcache管理一组mspan
- 当mcache中的mspan分配完毕,向mcentral申请带有未分配块的mspan
- 当mspan中没有分配的对象,mspan会被缓存在mcentral中,而不是立刻释放并归还给OS
内存管理的优化
对于高并发的Go语言来说:
- 对象分配是非常高频的操作:每秒分配GB级别的内存
- 空间较小的对象占比比较高
- Go内存分配比较耗时:1、分配路径长:g -> m -> p -> mcache -> mspan -> memory block -> return pointer。 2、pprof:对象分配的函数是最频繁调用的函数之一
Balanced GC
每个g都绑定一大块内存(1KB),称作goroutine allocation buffer(GAB)
GAB用于noscan类型的小对象分配:< 128B(设定的默认值)
使用三个指针维护GAB:base基地址, end结束地址, top当前地址
下图将阐释GAB的结构
从结构来说,GAB对于Go内存来说是一个大对象
GAB本质:将多个小对象的分配合并成一次大对象的分配
问题:如果GAB结构中只有一个小对象存活,那会使得GAB的对象分配方式会导致内存被延迟释放
解决问题的方案:
采用copying GC的思想,当GAB总大小超过一定阈值时,
将GAB中存活的对象复制到另外分配的GAB中,
然后释放原先的GAB