这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天
本篇文章主要是收集了笔者在学习今天的课程,在学习了一些golang的内存管理后,总结了一些注意点。
内存管理注意点
逃逸分析
简单来讲,逃逸分析决定一个变量是分配在堆上还是分配在栈上。在C++中,我们需要对变量进行手动的使用和销毁,但在Go中,销毁过程可以交给GC自己完成,使用的机制就是逃逸分析。
逃逸分析根据的最重要的一点就是,看这个变量的引用是否会被外部使用到,若是则分配在堆上,否则就优先分配到效率更高的栈上,若还是因为爆内存等原因则才分配到堆上。可以使用go build -gcflags '-m'命令来查看逃逸分析的结果。
这也就解释了为什么一些函数里,函数参数和返回值如果使用指针类型,只需要拷贝内存地址,但最终效率却比直接拷贝数值传递的效率要低,因为后者的变量内存可能由编译器判断后放置在栈中,而前者则一般都会放在堆中。
内存分配
在程序启动时,go就会申请一份内存。这份内存会划分为三个区域,占用大小从小到大分别是:spans区域、bitmap区域和arena区域。其中:
- arena区域就是对应堆的区域,组成它的单位是mspan,每个mspan又有多个8KB大小的页组成。
- mspan是go内存管理的基本单元。每个mspan又会划分为多个object,每个object存储一个对象。
- bitmap区域保存了arena区域中保存的对象的各个地址标识。
- spans区域保存了arena区域中各个mspan的指针。可以根据指针快速找到对应的mspan。
go内存管理的三大内存分配器是:mcache, mcentral, mheap。
- mcache:不需要锁。会在本地缓存可用的mspan资源,以此直接分配给协程。当被申请资源却没有mspan资源时,则会向mcentral申请。
- mcentral:需要锁。保存一个mspan列表,其中含已分配和未分配的标记,以此分配给mcache。当被申请资源却没有mspan资源时,则会向mheap申请。
- mheap:需要锁。代表程序中的所有堆空间。当被申请资源却没有mspan资源时,则会向操作系统申请。
go内存管理中的对象根据大小分为三种:小对象(小于等于16B)、一般对象(大于16B,小于等于32KB)、大对象(大于32KB)。 其中,大对象直接向mheap申请资源,而小对象和一般对象则尽量向mcache申请资源:
- 小对象通过mcache的tiny分配器分配资源。
- 一般对象通过计算后由mcache分配mspan资源,然后根据mspan资源的有无层层向上递归申请。
以上的内存分配和分配器的讲解比较简化,仅供简单了解,若想了解更多如分配器分配mspan时的具体流程,则应该去搜索其他文章。推荐文章:图解Go语言内存分配 - 知乎 (zhihu.com)