golang内存逃逸

213 阅读2分钟

什么是内存逃逸

内存逃逸(Memory Escape)是指在函数内部创建的对象(如变量、切片、映射、接口等)在函数执行结束后仍然被其他地方引用,从而导致这些对象不再局限于函数的栈帧(stack frame)上,而是被分配到了堆(heap)上。 Golang 使用自动垃圾回收(Garbage Collection,GC)来管理内存,而在栈上分配内存比在堆上分配内存更加高效。当一个函数创建一个对象并将其返回,或者将其传递给其他函数,而该对象在函数结束后仍然被引用,这就会导致内存逃逸。 可以使用以下命令来查看逃逸分析结果: go build -gcflags '-m -m -l'

发生场景

  1. 将对象分配给全局变量或者包级变量,这些变量的生命周期超出了函数的范围。
  2. 从函数中返回一个对象,或者将对象传递给其他函数,并且这些函数继续引用该对象。
  3. 在闭包中引用函数的局部变量,使得该局部变量的生命周期延长到了闭包的生命周期。
  4. 在多个 goroutine 中共享一个对象,这样对象的生命周期就不再局限于单个 goroutine。

示例

  1. 将对象返回给其他函数并在函数内部调用
func useObject(obj *SomeStruct) {
    // 在 useObject 函数中继续引用传入的对象,导致对象逃逸到堆上
    fmt.Println(obj.field1)
}

func main() {
    // 在主函数中创建一个对象
    obj := SomeStruct{field1: "value1", field2: "value2"}
    // 将对象传递给 useObject 函数
    useObject(&obj)
}
  1. 在闭包中引用函数的局部变量
func closureExample() func() {
    // 在闭包中引用了局部变量 x,导致 x 逃逸到堆上
    x := 10
    return func() {
        fmt.Println(x)
    }
}
  1. 创建 goroutine 并共享对象
func goroutineExample() {
    // 创建一个 goroutine 并共享对象 obj
    obj := SomeStruct{field1: "value1", field2: "value2"}
    go func() {
        fmt.Println(obj.field1)
    }()
    // goroutine 中继续引用 obj,导致对象逃逸到堆上
}
  1. 将对象分配给全局变量或包级变量
var globalObj *SomeStruct

func assignToObject() {
    // 在函数内部创建一个对象并分配给全局变量 globalObj
    globalObj = &SomeStruct{field1: "value1", field2: "value2"}
    // 对象逃逸到堆上,因为全局变量的生命周期超出了函数的范围
}