全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-13 8 分钟 ✍️ juanwangdev

Go内存分配原理详解

Go内存分配器采用多级缓存结构,优化分配性能。

三层结构概览

Go
┌─────────────────────────────────────┐
│            mheap                     │
│  (全局堆,从OS申请内存arena)         │
└─────────────────────────────────────┘
              ↑ 向上提供
              │
┌─────────────────────────────────────┐
│           mcentral                   │
│  (中央缓存,67种size class)         │
└─────────────────────────────────────┘
              ↑ 向上提供
              │
┌─────────────────────────────────────┐
│           mcache                     │
│  (本地缓存,每P独享,无锁)           │
└─────────────────────────────────────┘
              ↑
         P(处理器)

mcache本地缓存

结构

Go
type mcache struct {
    alloc [67]*mspan  // 67种大小类别的span
    // ...
}

// 每个P独享一个mcache
type p struct {
    mcache *mcache
}

无锁分配

Go
// 分配流程
func mallocgc(size uintptr) unsafe.Pointer {
    // 1. 确定size class
    sizeclass := sizeToClass(size)

    // 2. 从当前P的mcache获取
    c := getcurrentp().mcache
    span := c.alloc[sizeclass]

    // 3. 从span分配对象(无锁)
    obj := span.alloc()
    return obj
}

mcache绑定P,同一时刻只有一个G使用,无需锁。

mcentral中央缓存

结构

Go
type mcentral struct {
    sizeclass int8
    partial   mSpanList  // 有空闲对象的span
    full      mSpanList  // 无空闲对象的span
}

// 全局67个mcentral
var mcentral [67]mcentral

职责

Go
// mcache空了向mcentral申请
func (c *mcentral) cacheSpan() *mspan {
    // 需要加锁(但调用频率低)
    lock(&c.lock)

    // 从partial链表获取
    span := c.partial.pop()

    unlock(&c.lock)
    return span
}

mheap全局堆

结构

Go
type mheap struct {
    arenas []arena  // 内存区域
    // ...
}

type arena struct {
    spans [pagesPerArena]*mspan
}

// arena通常是64MB(Linux)

向OS申请内存

Go
func (h *mheap) allocSpan(npages uintptr) *mspan {
    // 使用mmap系统调用
    addr := sysAlloc(npages * pageSize)
    // 组织成span
    span := &mspan{startAddr: addr, npages: npages}
    return span
}

size class大小类别

67种预定义大小

Go
// 8B到32KB,共67种
sizeclasses = [...]uint16{
    8, 16, 24, 32, 48, 64, 80, 96, 112, 128,
    144, 160, 176, 192, 208, 224, 240, 256,
    // ... 直到32KB
}

分配策略

Go
// 分配15字节
// → 使用size class 2(16字节)
// 浪费1字节,但减少碎片

// 分配超过32KB
// → 直接从mheap分配,不经过mcentral

span内存块

结构

text
type mspan struct {
    startAddr  uintptr   // 起始地址
    npages     uintptr   // 页数(每页8KB)
    sizeclass  uint8     // 大小类别
    nelems     uint16    // 对象数量
    allocCount uint16    // 已分配数量
    freeindex  uintptr   // 下一个空闲索引
}

示例

text
// size class 1(8字节)
// span大小:1页 = 8KB
// 对象数量:8KB / 8B = 1024个

// span内部布局:
// [obj8][obj8][obj8]...[obj8]
// 共1024个8字节槽位

分配流程总结

text
┌─────────────────────────────────────┐
│          分配请求                    │
└─────────────────────────────────────┘
              ↓
┌─────────────────────────────────────┐
│     检查大小是否超过32KB              │
└─────────────────────────────────────┘
       ↓ 小于           ↓ 大于
┌──────────────┐   ┌──────────────┐
│ 确定sizeclass│   │ 直接从mheap  │
└──────────────┘   │ 分配大块     │
       ↓           └──────────────┘
┌─────────────────────────────────────┐
│    从P的mcache获取对应span            │
└─────────────────────────────────────┘
       ↓ 有空间          ↓ 无空间
┌──────────────┐   ┌──────────────┐
│ 直接分配     │   │ 从mcentral   │
│ (无锁)     │   │ 申请新span   │
└──────────────┘   └──────────────┘
                     ↓ mcentral空
                  ┌──────────────┐
                  │ 从mheap申请  │
                  └──────────────┘
                     ↓ mheap空
                  ┌──────────────┐
                  │ 向OS申请内存 │
                  │ (mmap)     │
                  └──────────────┐

各层职责对比

层级位置频率说明
mcache每P极高大部分分配在此完成
mcentral全局67个mcache空时补充
mheap全局极低mcentral空时补充
OS系统-最少mheap空时申请

要点总结

  • 三层结构:mcache→mcentral→mheap
  • mcache绑定P,无锁分配
  • 67种size class,8B-32KB
  • 超过32KB直接从mheap分配
  • span是基本分配单元
  • 分配优先级:mcache→mcentral→mheap→OS
  • mcache无锁是性能关键

📝 发现内容有误?点击此处直接编辑

← 上一篇 Go竞态检测与并发调试详解
下一篇 → Go内存逃逸分析详解
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库