原文:详解 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 中分配