内存分配组件
在 Golang 中,mcache、mcentral 和 mheap 是内存管理的三大组件,mcache 管理线程在本地缓存的 mspan,页 mcentral 管理着全局的 mspan 为所有 mcache 提供所有线程。
- mcache
- 在GPM关系中,会在每个 P 下都有一个 mcache 字段,用来表示内存信息。
- P 运行只占用一份 mcache,对于 mcache 的数量就是P 的数量,同时并发访问时也不会产生锁。
- mspan
- mspan 是分配内存时的基本单元。
- Go将内存块分为大小不同的 67 种,然后再把这 67 种大内存块,逐个分为小块(可以近似理解为大小不同的相当于page)称之为span(连续的page),在go语言中就是上文提及的mspan。
- mcentral
- mentral 是一个空闲列表。
- 实际上 mcentral 它并不包含空闲对象列表,真正包含的是 mspan 。
- 每个mcentral 是两个 mspans 列表:空闲对象 c->notempty 和 完全分配对象 c->empty,如图所示
- mheap
- 堆内存
Go 没法使用工作线程的本地缓存 mcache 和全局中心缓存 mcentral 上管理超过32KB的内存分配
所以对于那些超过32KB的内存申请,会直接从堆上(mheap)上分配对应的数量的内存页(每页大小是8KB)给程序。
内存分配时机
已切片为例,初始化切片的源代码为例来看看切片何时被分配到堆上的逻辑判断:
理论上直接分配到栈内存上
- 编译器进行逃逸分析,判断并标记该变量是否需要分配到堆上
- 否:直接分配在栈上
- 是:调用src/runtime/slice.go::makeslice()分配到堆上
go内存分配图
总结
-
Go语言源代码中「栈内存」和「堆内存」的分配都是虚拟内存,最终CPU在执行指令过程中通过内部的MMU把虚拟内存转化为物理内存。
-
Go语言编译期间会进行逃逸分析,判断并标记变量是否需要分配到堆上,比如创建Map、Slice时。
栈内存分配
- 小于32KB的栈内存
- 来源优先级1:线程缓存mcache
- 来源优先级2:全局缓存stackpool
- 来源优先级3:逻辑处理器结构p.pagecache
- 来源优先级4:堆mheap
- 大于等于32KB的栈内存
- 来源优先级1:全局缓存stackLarge
- 来源优先级2:逻辑处理器结构p.pagecache
- 来源优先级3:堆mheap
堆内存分配
- 微对象 0 < Micro Object < 16B
- 来源优先级1:线程缓存mcache.tiny
- 来源优先级2:线程缓存mcache.alloc
- 小对象 16B =< Small Object <= 32KB
- 来源优先级1:线程缓存mcache.alloc
- 来源优先级2:中央缓存mcentral
- 来源优先级3:逻辑处理器结构p.pagecache
- 来源优先级4:堆mheap
- 大对象 32KB < Large Object
- 来源优先级1:逻辑处理器结构p.pagecache
- 来源优先级2:堆mheap