GO内存管理 | 青训营笔记

102 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天

GO语言内存管理

栈内存

Go语言中栈内存也叫协程栈或者调用栈,协程栈中第一个栈帧是goexit(),goexit()是为了退出后重新调度使用的,同时协程栈中还记录了协程的执行路径。在函数中声明的局部变量,如果只是函数内部使用的话,那么这个变量会记录在协程栈里面。

   
func do1() {
  num := 1
  num = do2(num)
  fmt.Println(num)
 }

func do2(num int) int {
  num++
  return num
  }

C/C++中栈区和堆区是分开的,堆上的内存需要程序员自己去释放,栈上的内存由程序释放,但是Go语言中栈内存是从堆内存上申请的,初始空间为2KB,所以说Go协程栈位于Go堆内存上,而Go堆内存位于操作系统虚拟内存上。

堆内存

1.golang堆内存分配采用和tcmalloc内存分配器类似的算法

2.堆内存划分为一个个arena空间,arena的初始地址记录在arenaBaseOffset中,在amd64架构的linux中,其值默认为64M,每个arena中有8192个page,每个page有8KB。

3.golang将内存默认分为68种大小规格,最小为8B,最大为32KB,大于32的独立分给一种类型(0),同一种规格又区分为可扫描和不可扫描(标量和指针),所有总共有136种mspan。

4.一个arena划分为多个span,一个span包含1到多个page,并固定划分为某种规格的内存块。

内存分配的过程

分配内存前,会按照对象的大小进行不同的分配,分配逻辑如下:

  • 0-16字节不包含指针的对象:Tiny微小对象分配,从mcache中拿到一个2级的mspan,将多个微对象合并成一个16B对象存入2级mspan。
  • 0-16字节包含指针的对象和16B-32KB的对象:正常对象分配至mspan。
  • 32KB以上的大对象:使用0级mspan分配,0级mspan没有固定大小,专为大对象分配。

申请内存过程

  1. 获取当前协程的私有缓存mcache。
  2. 根据申请内存的大小计算出合适的mspan编号。
  3. 从mcache的成员alloc中查询可用的mspan。
  4. 如果mcache中没有可用的mspan,则从mcentral中申请一个新的mspan加入mcache中。
  5. 如果mcentral中也没有可用的mspan,则从mheap中获取一个新的mspan加入mcentral。
  6. 如果mheap也没有可用内存,那么就会向操作系统再次申请新的内存块heapArena供Go程序使用。