# 一、 核心概念:栈 (Stack) vs 堆 (Heap)

特性栈 (Stack)堆 (Heap)
分配速度极快(移动栈顶指针)较慢(需寻找合适内存块)
管理方式编译器自动分配与释放由 GC (垃圾回收器) 管理释放
生存周期随函数调用开始 / 结束持续到不再被引用,由 GC 回收
碎片容易产生碎片

# 二、 逃逸分析 (Escape Analysis)

Go 编译器会自动决定变量分配在栈还是堆。原则: 如果变量在函数结束后仍被引用,则必须逃逸到堆。

# 1. 指针逃逸

场景: 函数返回局部变量的地址。

func createPointer() *int {
    x := 10     // 本地变量 x
    return &x   //x 逃逸:外部需要访问这个地址
}

# 2. Interface 逃逸

场景: 变量作为参数传给 interface{} 类型函数(如 fmt.Println )。

func main() {
    name := "Larry"
    fmt.Println(name) //name 会逃逸
}
// 解析:fmt.Println 接收空接口,编译器无法在编译期确定其动态类型。

# 3. 闭包捕获逃逸

场景: 内部函数引用了外部函数的局部变量。

func increase() func() int {
    n := 0
    return func() int {
        n++ //n 逃逸:因为闭包的生命周期长于 increase 函数
        return n
    }
}

# 4. 动态大小或巨型变量

场景: 变量太大导致栈空间不足,或者切片长度在编译期无法确定。

func sliceEscape() {
    // 长度是变量,编译期不确定,可能逃逸
    size := 10
    s := make([]int, size) 
    
    // 或者对象过大,如:
    // bigObj := make([]int, 10000)
}

# 三、 Go 内存分级布局 (TCmalloc 思想)

Go 内存分配器的三层架构(为了实现无锁分配,提高并发性能):

  1. mcache: 每个 P (Processor) 私有的缓存。无锁,速度最快。
  2. mcentral: 全局中央缓存。当 mcache 耗尽时,向其申请。有锁竞争。
  3. mheap: 堆内存总量。当 mcentral 不足时,向操作系统申请大块内存。

# 四、 面试高频追问

  • Q: 逃逸分析有什么好处?

  • A: 1. 减少堆内存分配,减轻 GC 负担。 2. 栈分配比堆分配效率更高。 3. 提高缓存命中率。

  • Q: 指针传递一定比值传递快吗?

  • A: 不一定。如果指针传递导致变量从栈逃逸到堆,那么堆分配和后续 GC 的开销可能远大于值拷贝的开销。

  • Q: 如何手动查看逃逸结果?

  • A: 执行 go build -gcflags="-m -l" main.go


# 五、 实战建议 (周一复习指南)

  1. 对比实验:写出上述三个案例,运行 -gcflags="-m" 命令观察输出。
  2. 阅读源码:如果精力允许,看一眼 runtime/malloc.go 中关于 mcache 的注释。
  3. 结合并发:思考为什么 mcache 放在 P 上而不是 G 上?(提示:P 保证了并发下的亲和性)。
更新于 阅读次数