逃逸分析

逃逸分析,就是一些变量或对象在函数调用过程中没有被分配到栈内存,而被分配到堆内存上。简单来说就是这些变量在函数调用结束的时候没有被销毁,因为某些原因保留在堆内存上,导致内存使用可能不符合预期。 内存逃逸:当 Go 的编译器发现一个局部变量在函数结束后仍然会被使用(比如它的引用被传递到其他地方),它就无法将这个变量分配到栈上,只能分配到堆上。这就是“内存逃逸”。

举个例子 假设你有一个函数创建了一个局部变量,并返回它的引用。如果编译器发现这个返回的引用在函数调用结束后仍然会被使用,那么它就会将这个局部变量分配到堆上,而不是栈上。这样可以确保这个变量在函数返回后仍然有效,但也会导致更多的内存分配和垃圾回收开销。

后果 内存逃逸会导致更高的内存开销和更频繁的垃圾回收,因为堆上的内存需要更多的管理和维护。理解并优化内存逃逸现象可以帮助提高程序的性能和效率

栈和堆的区别

  • 栈是一个临时的内存区域,用于存储函数的局部变量,函数调用结束后这些变量会自动释放。
  • 堆是一个长期的内存区域,用于存储需要长期存在的数据,必须手动管理释放。

场景

返回函数内部变量的引用:

func createClosure() *int {
    x := 10
    return &x // 返回局部变量的指针
}

闭包

func makeIncrementer(start int) func() int {
    count := start
    return func() int {
        count++
        return count
    }
}

将局部变量传递到 Goroutine 中:

func startGoroutine() {
    x := 10
    go func() {
        fmt.Println(x) // 在 Goroutine 中使用局部变量
    }()
}

反射和接口: 使用反射(reflect 包)或接口(interface)时,编译器可能无法确定数据的实际类型或大小,因此会将数据分配到堆上,以便在运行时动态管理。

func printValue(v interface{}) {
    fmt.Println(v) // 使用接口类型,可能导致数据逃逸到堆
}