本文最新版本请查看原文:https://blog.haohtml.com/archives/29385
Golang中的内存部件组成关系如下图所示

golang 内存分配组件
在学习golang 内存时,经常会涉及几个重要的数据结构,如果不熟悉它们的情况下,理解它们就显得格外的吃力,所以本篇主要对相关的几个内存组件做下数据结构的介绍。
在 Golang 中,mcache
、mspan
、mcentral
和 mheap
是内存管理的四大组件,mcache
管理线程在本地缓存的 mspan
,页 mcentral 管理着全局的 mspan 为所有 mcache 提供所有线程。
根据分配对象的大小,内部会使用不同的内存分配机制,详细参考函数 mallocgo()
-
<16b
会使用微小对象内存分配器,主要使用mcache.tinyXXX
这类的字段 -
16-32b
从P下面的mcache
中分配 -
>32b
直接从mheap
中分配
对于golang中的内存申请流程,大家应该都非常熟悉了,这里不再进行详细描述。

mcache
在GPM关系中,会在每个 P
下都有一个 mcache
字段,用来表示内存信息。
在 Go 1.2 版本前调度器使用的是 GM
模型,将mcache
放在了 M
里,但发现存在诸多问题,期中对于内存这一块存在着巨大的浪费。每个M
都持有 mcache
和 stack 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 并发请求内存时是无锁的。

当申请一个 16b
大小的内存时,会优先从运行当前G所在的P
里的mcache
字段里找到相匹配的mspan
规格,此时最合适的是图中 mspan3
规格。
mcache是从非GC内存中分配的,所以任何一个堆指针都必须经过特殊处理。源码文件:https://github.com/golang/go/blob/go1.16.2/src/runtime/mcache.go
type mcache struct {
// 下方成员会在每次访问malloc时都会被访,所以为了更加高效的缓存将按组其放在这里
nextSample uintptr // trigger heap sample after allocating this many bytes
scanAlloc uintptr // bytes of scannable heap allocated
// 小对象缓存,<16b。推荐阅读"Tiny allocator"注释文档
tiny uintptr
tinyoffset uintptr
tinyAllocs uintptr
// 下方成员不会在每次 malloc 时被访问