0%

Go内存管理

原文:详解 Go 语言的内存模型及堆的分配管理 ,建议 tm 反复观看

golang 的内存管理是借鉴了 TCMalloc(Thread Cache Malloc),随着 Go 的迭代,Go 的内存管理与 TCMalloc 不一致的地方也在扩大,但其主要思想是一致的。只是比 TCMalloc 多了逃逸分析垃圾回收

基本架构

架构

  • page: 图中的浅蓝方块,在 x86 架构中一个 page 大小为 8KB
  • span: 内存管理的基本单位,由一组连续的 page 组成,图中的浅蓝长方形和紫色方块
  • mcache: mcache 保存的是各种大小的 Span,并按 Span class 分类,小对象直接从 mcache 分配内存,它起到了缓存的作用,并且可以无锁访问。Go 中是每个 P 拥有 1 个 mcache。在 Go 程序中,当前最多有 GOMAXPROCS 个线程在运行,所以最多需要 GOMAXPROCS 个 mcache 就可以保证各线程对 mcache 的无锁访问,线程的运行又是与 P 绑定的,把 mcache 交给 P 刚刚好。
  • mcentral: 所有线程共享的缓存,需要加锁访问。按 Span class 对 Span 分类,然后串联成链表,当 mcache 的某个 class 的 Span 的内存被分配光时,它会向 mcentral 申请 1 个当前级别的 Span。
  • mheap: 堆内存的抽象,把从 OS 申请出的内存页组织成 Span,并保存起来。当 mcentral 的 Span 不够用时会向 mheap 申请内存,而 mheap 的 Span 不够用时会向 OS 申请内存。mheap 向 OS 的内存申请是按页来的,然后把申请来的内存页生成 Span 组织起来,同样也是需要加锁访问的。mheap 把 Span 组织成了树结构,共 2 棵树,然后把 Span 分配到 heapArena 进行管理,它包含地址映射和 span 是否包含指针等位图,这样做的主要原因是为了更高效的利用内存:分配、回收和再利用。

大小对象

对象分类

  • 小对象(<= 32KB)直接在 mcache 中分配
  • 大对象(> 32KB)直接从 mheap 中分配

逃逸分析

原文见:golang 逃逸分析详解 ,建议 tmd 反复观看

1
2
3
go run -gcflags "-m -l" main.go
# -m 打印逃逸分析信息
# -l 禁止内联编译)

逃逸规则如下:

  1. 在某个函数内部new 生成的指针或者字面量的指针被当成函数返回值,则该变量一定发生了逃逸(构造函数返回的指针一定逃逸)
  2. 被 slice、map、chan 引用的指针一定发生了逃逸

    stack overflow 上有人提问为什么使用指针的 chan 比使用值的 chan 慢 30%,答案就在这里:使用指针的 chan 发生逃逸,gc 拖慢了速度。Why passing pointers to channel is slower

  3. 已经逃逸的变量引用的指针一定发生了逃逸