Golang 内存组件之mspan、mcache、mcentral 和 mheap 数据结构

Golang中的内存组件关系如下图所示

components of memory allocation
golang 内存分配组件

在学习golang 内存时,经常会涉及几个重要的数据结构,如果不熟悉它们的情况下,理解起来就显得格外的吃力,所以本篇主要对相关的几个内存组件做下数据结构的介绍。

在 Golang 中,mcachemspanmcentralmheap 是内存管理的四大组件,mcache 管理线程在本地缓存的 mspan,而 mcentral 管理着全局的 mspan 为所有 mcache 提供所有线程。

根据分配对象的大小,内部会使用不同的内存分配机制,详细参考函数 mallocgo()

  • <16KB 会使用微小对象内存分配器从 P 中的 mcache 分配,主要使用 mcache.tinyXXX 这类的字段
  • 16-32KBP 中的 mcache 中分配
  • >32KB 直接从 mheap 中分配

对于golang中的内存申请流程,大家应该都非常熟悉了,这里不再进行详细描述。

Golang 内存组件关系

mcache

在GPM关系中,会在每个 P 下都有一个 mcache 字段,用来表示内存信息。

在 Go 1.2 版本以前调度器使用的是 GM 模型,将 mcache 放在了 M 里,但发现存在诸多问题,其中对于内存这一块存在着巨大的浪费。每个 M 都持有 mcachestack alloc,但只有在 M 运行 Go 代码时才需要使用内存(每个 mcache 可以高达2mb),当 M 在处于 syscall网络请求 的时候是不需要内存的,再加上 M 又是允许创建多个的,这就造成了内存的很大浪费。所以从go 1.3版本开始使用了GPM模型,这样在高并发状态下,每个G只有在运行的时候才会使用到内存,而每个 G 会绑定一个P,所以它们在运行只占用一份 mcache,对于 mcache 的数量就是P 的数量,同时并发访问时也不会产生锁。

对于 GM 模型除了上面提供到内存浪费的问题,还有其它问题,如单一全局锁sched.Lock、goroutine 传递问题和内存局部性等。

P 中,一个 mcache 除了可以用来缓存小对象外,还包含一些本地分配统计信息。由于在每个P下面都存在一个mcache ,所以多个 goroutine 并发请求内存时是无锁的。

Continue reading