Go栈内存和堆内存

go的内存分配

Posted by 果果 on August 31, 2022

内存分配组件

在 Golang 中,mcache、mcentral 和 mheap 是内存管理的三大组件,mcache 管理线程在本地缓存的 mspan,页 mcentral 管理着全局的 mspan 为所有 mcache 提供所有线程。

  • mcache
    • 在GPM关系中,会在每个 P 下都有一个 mcache 字段,用来表示内存信息。
    • P 运行只占用一份 mcache,对于 mcache 的数量就是P 的数量,同时并发访问时也不会产生锁。

n3

  • mspan
    • mspan 是分配内存时的基本单元。
    • Go将内存块分为大小不同的 67 种,然后再把这 67 种大内存块,逐个分为小块(可以近似理解为大小不同的相当于page)称之为span(连续的page),在go语言中就是上文提及的mspan。

n4

  • mcentral
    • mentral 是一个空闲列表。
    • 实际上 mcentral 它并不包含空闲对象列表,真正包含的是 mspan 。
    • 每个mcentral 是两个 mspans 列表:空闲对象 c->notempty 和 完全分配对象 c->empty,如图所示

n5

  • mheap
    • 堆内存

Go 没法使用工作线程的本地缓存 mcache 和全局中心缓存 mcentral 上管理超过32KB的内存分配

所以对于那些超过32KB的内存申请,会直接从堆上(mheap)上分配对应的数量的内存页(每页大小是8KB)给程序。

n6

内存分配时机

已切片为例,初始化切片的源代码为例来看看切片何时被分配到堆上的逻辑判断:

理论上直接分配到栈内存上

  • 编译器进行逃逸分析,判断并标记该变量是否需要分配到堆上
    • 否:直接分配在栈上
    • 是:调用src/runtime/slice.go::makeslice()分配到堆上

go内存分配图

n2

总结

  • 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

「栈内存」也来源于堆mheap

n1