Go内存管理
go在程序运行时分配了不同的区空间来管理内存。
何为逃逸
go会将要执行的函数放入栈区,结束时会在栈区空间中释放,里面的局部变量也跟着释放。然而当该函数返回某个局部变量,并被外部函数调用时,该局部变量就不应该被放弃,于是go内存管理会在堆区中开辟一块空间,用于储存该局部变量的数据。我们看起来就像该变量从栈区跑到了堆区,便称之为逃逸。
而由编译器决定某变量分配到栈上还是堆上,便叫做逃逸分析。
有哪些逃逸
interface{} 逃逸
空接口interface{}可以表示任意类型,当作为函数参数传参时,编译器无法确定其类型,一次也会发生逃逸,分配到堆上。
示例:
func main() {
s := "abc"
fmt.Println(s)
}
这里使用fmt.Println打印了s变量,由于fmt.Println的参数为interface{}, 于是发生逃逸。
type any = interface{}
func Println(a ...any) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
指针逃逸
函数声明一个对象,之后返回该对象的指针,那么随着函数的结束,该指针并未被回收,而是分配到了堆上,即指针逃逸了。
示例:
package main
type student struct {
name string
}
func demo(name string) *student {
s := &student{name: name}
return s
}
func main() {
_ = *demo("李四") // 使用返回值
}
使用 go run -gcflags '-m -l' escape.go 来查看逃逸情况。
xxx escapes to heap 即表示xxx逃逸了,这里发现student指针发生逃逸。
闭包逃逸
函数中嵌套一层函数,那么嵌套函数可访问上一层函数的作用域,此为闭包。而在闭包中,嵌套函数由于使用了上层函数的变量,该变量无法随着嵌套函数释放而释放,因此也被逃逸到堆上。
示例
func demo() func() int {
n := 0
return func() int {
n++
return n
}
}
func main() {
demo()
}
总结
- 逃逸分析是在编译阶段执行
- 由于栈上分配内存比堆上分配内存效率更高,因此减少逃逸能提高性能