什么是内存逃逸
内存逃逸(Memory Escape)是指在函数内部创建的对象(如变量、切片、映射、接口等)在函数执行结束后仍然被其他地方引用,从而导致这些对象不再局限于函数的栈帧(stack frame)上,而是被分配到了堆(heap)上。
Golang 使用自动垃圾回收(Garbage Collection,GC)来管理内存,而在栈上分配内存比在堆上分配内存更加高效。当一个函数创建一个对象并将其返回,或者将其传递给其他函数,而该对象在函数结束后仍然被引用,这就会导致内存逃逸。
可以使用以下命令来查看逃逸分析结果:
go build -gcflags '-m -m -l'
发生场景
- 将对象分配给全局变量或者包级变量,这些变量的生命周期超出了函数的范围。
- 从函数中返回一个对象,或者将对象传递给其他函数,并且这些函数继续引用该对象。
- 在闭包中引用函数的局部变量,使得该局部变量的生命周期延长到了闭包的生命周期。
- 在多个 goroutine 中共享一个对象,这样对象的生命周期就不再局限于单个 goroutine。
示例
- 将对象返回给其他函数并在函数内部调用
func useObject(obj *SomeStruct) {
// 在 useObject 函数中继续引用传入的对象,导致对象逃逸到堆上
fmt.Println(obj.field1)
}
func main() {
// 在主函数中创建一个对象
obj := SomeStruct{field1: "value1", field2: "value2"}
// 将对象传递给 useObject 函数
useObject(&obj)
}
- 在闭包中引用函数的局部变量
func closureExample() func() {
// 在闭包中引用了局部变量 x,导致 x 逃逸到堆上
x := 10
return func() {
fmt.Println(x)
}
}
- 创建 goroutine 并共享对象
func goroutineExample() {
// 创建一个 goroutine 并共享对象 obj
obj := SomeStruct{field1: "value1", field2: "value2"}
go func() {
fmt.Println(obj.field1)
}()
// goroutine 中继续引用 obj,导致对象逃逸到堆上
}
- 将对象分配给全局变量或包级变量
var globalObj *SomeStruct
func assignToObject() {
// 在函数内部创建一个对象并分配给全局变量 globalObj
globalObj = &SomeStruct{field1: "value1", field2: "value2"}
// 对象逃逸到堆上,因为全局变量的生命周期超出了函数的范围
}