Skip to content

这里整理 Go 内存分配器中 mspan、mcache、mcentral、mheap 和 heapArena 的职责与使用场景,以及它们与 Thread Cache、Central Cache 和 Heap 三层结构的对应关系。

内存分配器的分层结构 ​

Go 的内存分配器分三层,从上到下是:

  1. Thread Cache(线程缓存)
  2. Central Cache(中央缓存)
  3. Heap(堆)

关键组件及其关系 ​

对应到运行时实现,三层分别落在 mcache、mcentral、mheap 上,加上 mspan 和 heapArena 两个基础结构,一共五个关键组件。

关系图示 ​

Thread Cache (mcache)
       |
Central Cache (mcentral)
       |
Heap (mheap)
       |
heapArena

组件详解及使用场景 ​

1. Thread Cache(线程缓存)/ mcache ​

mcache 是每个 P(Processor)私有的缓存,用于分配小对象(小于 32 KB)。它包含多个 mspan,每个 mspan 用于存储相同大小类别的对象。

使用场景:

  • 适用于频繁分配和释放的小对象,减少线程之间的竞争,提高分配效率。

例子:

go
func allocateSmallObject(size int) {
    // 分配一个小对象(< 32 KB)
    mem := make([]byte, size)
    // 使用内存防止被优化掉
    mem[0] = 1
    fmt.Println("Allocated small object")
}

2. Central Cache(中央缓存)/ mcentral ​

mcentral 管理每个大小类别的 mspan,负责从 mheap 获取新的 mspan,并将用完的 mspan 归还给 mheap,是 mcache 和 mheap 之间的中间层。

使用场景:

  • 当 mcache 中的内存不足时,从 mcentral 获取新的 mspan。
  • 当 mcache 中有多余内存时,将其归还给 mcentral。

例子(运行时内部伪代码示意,不可直接编译运行):

go
func allocateFromCentralCache(sizeClass int) {
    // 从中央缓存分配内存
    span := mcentral.alloc(sizeClass)
    // 使用 span 分配对象
    obj := span.allocateObject()
    fmt.Println("Allocated object from central cache")
}

3. Heap(堆)/ mheap ​

mheap 是全局的堆内存管理器,负责从操作系统获取内存并管理 mspan 的分配和回收。它提供大块内存(页),并将这些内存分成 mspan 供 mcentral 使用。

使用场景:

  • 适用于大对象分配(>= 32 KB)。
  • 当 mcentral 请求新的 mspan 时,从 mheap 获取内存。

例子:

go
func allocateLargeObject(size int) {
    // 分配一个大对象(>= 32 KB)
    mem := make([]byte, size)
    // 使用内存防止被优化掉
    mem[0] = 1
    fmt.Println("Allocated large object")
}

4. mspan ​

mspan 是一组连续的内存页,用于分配对象。mspan 按大小类别(class)划分,每个类别包含大小相似的对象。

使用场景:

  • 管理相同大小的对象,以提高分配和释放效率。
  • 由 mcache 和 mcentral 使用,用于对象分配。

例子(运行时内部伪代码示意,不可直接编译运行):

go
func allocateFromSpan(span *mspan, size int) {
    // 从 span 分配对象
    obj := span.allocate(size)
    fmt.Println("Allocated object from span")
}

5. heapArena ​

heapArena 是 mheap 从操作系统获取的大块内存区域,每个 heapArena 包含 64 MB 的内存。用于管理和分配内存页。

使用场景:

  • mheap 从操作系统获取内存时,将其分割成 heapArena。
  • 提供基础内存给 mheap 管理。

例子(运行时内部伪代码示意,不可直接编译运行):

go
func allocateFromHeapArena(size int) {
    // 从 heapArena 分配内存
    arena := newHeapArena(size)
    fmt.Println("Allocated memory from heap arena")
}

内存分配流程 ​

  1. 小对象分配(<32 KB)

    • 当 goroutine 请求分配一个小对象时,mcache 首先检查是否有足够的内存可用。
    • 如果 mcache 有足够的内存,直接分配并返回对象。
    • 如果 mcache 没有足够的内存,会向 mcentral 请求一个新的 mspan。
    • 如果 mcentral 没有可用的 mspan,会向 mheap 请求一个新的 mspan。
  2. 大对象分配(>=32 KB)

    • 当 goroutine 请求分配一个大对象时,直接向 mheap 请求内存。
    • mheap 分配相应大小的内存页并返回。

总结 ​

Go 的内存分配器按 mcache、mcentral、mheap 三层分工:小对象在 P 私有的 mcache 上就地分配,线程之间几乎不竞争;不够了才向 mcentral 要 mspan,再不够由 mheap 向操作系统要内存。分层可能带来内存碎片,换来的是贴合 Go 并发模型的分配速度。

下面是整合的代码示例,展示了各个组件的使用:

go
package main

import (
    "fmt"
    "sync"
)

func allocateSmallObject(size int) {
    // 分配一个小对象(< 32 KB)
    mem := make([]byte, size)
    // 使用内存防止被优化掉
    mem[0] = 1
    fmt.Println("Allocated small object")
}

func allocateFromCentralCache(sizeClass int) {
    // 模拟从中央缓存分配内存
    fmt.Println("Allocated object from central cache of size class", sizeClass)
}

func allocateLargeObject(size int) {
    // 分配一个大对象(>= 32 KB)
    mem := make([]byte, size)
    // 使用内存防止被优化掉
    mem[0] = 1
    fmt.Println("Allocated large object")
}

func allocateFromSpan(spanSize, objSize int) {
    // 模拟从 span 分配对象
    fmt.Printf("Allocated object of size %d from span of size %d\n", objSize, spanSize)
}

func allocateFromHeapArena(size int) {
    // 模拟从 heapArena 分配内存
    fmt.Println("Allocated memory from heap arena of size", size)
}

func main() {
    var wg sync.WaitGroup
    wg.Add(3)

    // 小对象分配
    go func() {
        defer wg.Done()
        allocateSmallObject(16 * 1024) // 16 KB
    }()

    // 从中央缓存分配对象
    go func() {
        defer wg.Done()
        allocateFromCentralCache(1) // Size class 1
    }()

    // 大对象分配
    go func() {
        defer wg.Done()
        allocateLargeObject(64 * 1024) // 64 KB
    }()

    wg.Wait()
    fmt.Println("All allocations finished")
}

这个例子串了一遍小对象、大对象和中央缓存三条分配路径;真正的分配逻辑都在 Go 运行时内部,应用代码看到的只是 make 这类调用。

参考资料 ​

相关页面 ​

Hello Golang