你了解内存管理吗?

栈分配和堆分配确实都发生在“对象分配器”的分配阶段, 但它们走的是不同的路径: 栈分配走的是编译期静态分配; 堆分配走的是运行时对象分配器(runtime.mallocgc)。 1.程序启动阶段 Go 运行时启动时(runtime 初始化),会: 向操作系统申请一大块虚拟内存(称为 arena); 由 页分配器(page allocator) 管理这块内存; 构建堆内存管理结构(mheap、mcentral、mcache)。 这部分只是“预留”内存,真正的对象分配还没发生。 2.用户程序触发分配(对象分配阶段) 当用户代码中创建变量时,比如: x := MyStruct{} 编译器会在编译阶段决定这个对象是: 分配在栈上(stack allocation) 还是分配在堆上(heap allocation) 这个决策是通过 逃逸分析(Escape Analysis) 完成的。 3.栈分配的过程 如果编译器认为对象只在当前函数作用域内使用,不会被外部引用: 这个对象会直接分配在栈上; 不会调用运行时的内存分配器; 栈内存是随函数调用帧自动增长/释放的; GC 不需要扫描或回收它。 > 关键:栈分配是编译期确定的,性能最好。 4.堆分配的过程 如果对象被闭包引用、返回地址或传递给其他 goroutine,则会发生逃逸: 编译器在生成代码时,会调用运行时的分配器 runtime.mallocgc; mallocgc 会从当前 P 的 mcache 尝试获取一个合适的 span; 若 mcache 缓存不足,就从 mcentral → mheap 逐层申请; 分配完成后,GC 会在堆上追踪这个对象。 >关键:堆分配是运行时动态完成的,涉及 GC 管理。 5.回收阶段 当对象不再被引用时,GC 会标记并清除; 被清除的内存重新回收到 mcache / mcentral / mheap; 长期未使用的页可能由scavenger(拾荒器)归还给 OS。 6.对比栈和堆分配 类型 分配阶段 分配位置 分配速度 是否由 GC 管理 是否逃逸 栈分配 编译期(静态) 每个 goroutine 的调用栈 极快 否 否 堆分配 运行时(动态) 运行时堆(mheap) 慢 是 是

2025年10月14日 · Mumu