这是我参与「第五届青训营 」伴学笔记创作活动的第6天。
主要是记录视频当中的Go内存管理方法。
一、Go内存分配——分块
目标:为对象在heap上分配内存
上节课提到,heap是一块存放对象的区域。垃圾回收算法会把这个区域寻中死亡对象回收。
为了方便搜索这些对象,内存分配工作会提前对这个区域分块,并把对象存放到与其尺寸最接近的内存。
具体操作如下:
1.首先调用系统调用mmap()向操作系统申请一大块内存。
2.将大内存切割成大块,例如图中8KB一块,称为mspan。
3.再将大块继续划分成特定大小的小块,用于对象分配。如下图所示,如果有一个小于8B的对象则会被存放在第一排的mspan,如果有一个大于8B小于16B的对象,我们会把对象存放在第二排的mspan。
其中这里存放的对象有两种特征:
1.分配不包含指针的对象,则这整块mspan为noscan mspan。此区域GC不需要扫描
2.分配包含指针的对象,则这整块mspan为scan mspan。此区域GC需要扫描
从上述我们可以得知,noscan mspan是不需要GC扫描。而scan mspan是需要GC扫描,则这个区域是我们需要判别这个区域的对象是死亡对象还是存活对象,GC会对这个区域的对象进行垃圾回收。
综上,根据对象的大小以及是否包含指针,我们会选择最合适的块来存储对象。
二、Go内存分配——缓存
我们这里由下图从上到下介绍Go是如何进行缓存的。下图是一种多级缓存的方式来加快程序中缓存进行的速度。
首先从对象进入开始
我们分配内存一般是从goroutine上执行的代码开始(这里我们标注为g)。这里的m指的是内核线程,记录内核的线程栈信息,当goroutine调度到线程的时候,使用自身的栈信息。然后根据对象的大小通过p来快速匹配mcache(负责管理一组mspan)中合适的mspan。这里p是负责调度goroutine,也就是负责帮助对象进行内存管理。
而p存有一组mspan,每个mspan负责存储的大小是不一样的。
p会根据对象的实际大小,找到大小合适,且要有空余块的mspan,如上图所示。p会把这个块返回给对象,就完成了一次对象分配。
但是,如果合适大小的mspan全部被占用了,已经没有空余的块来装对象的时候。此时mcache会向mcentral申请还有空余块且大小合适的mspan,把它填入mcache。再从这个新加入的mspan中返回一个块,完成内存分配。
如果mcache中,存在跟下图类似完全为空的mspan。Go的内存分配器会把这个mspan先返回给mcentral,把它进行缓存,而不是直接释放返还给操作系统。如果mcache又出现该大小的内存块不够时,可以再把这个mspan调用出来。
但是不是一直放在mcentral不丢了,代码会有一定的策略来把这些完全空余的mspan丢给操作系统。
总结
Go的对象分配是一个高频操作,并且小对象占比非常高。而且缓存机制可以看出分配路径很长,比较耗时。
后面的看不懂了。