golang内存逃逸
嘻嘻发布于2022-07-19
最后更新于2022年7月19日
浏览栈是线程级别的,大小在创建的时候已经确定,当变量太大的时候,会"逃逸"到堆上,这种现象称为内存逃逸。
内存逃逸的场景
指针逃逸
Go可以返回局部变量指针,这种情况下,函数虽然退出了,但是因为指针的存在,对象的内存不能随着函数结束而回收,因此只能分配在堆上。
package main
type User struct {
Name string
}
func foo(s string) *User {
u := new(User)
u.Name = s
return u // 1.方法内局部变量返回,逃逸
}
func main() {
user := foo("gui")
user.Name = "dev"
}
/* 逃逸分析日志
# command-line-arguments
./main.go:11:6: can inline foo
./main.go:17:6: can inline main
./main.go:18:13: inlining call to foo
./main.go:11:10: leaking param: s
./main.go:12:10: new(User) escapes to heap # 逃逸
./main.go:18:13: new(User) does not escape
*/
栈空间不足逃逸
当栈空间不足以存放当前对象时或无法判断当前切片长度时会将对象分配到堆中。
interface{} 动态类型逃逸
在 Go 中,空接口 interface{} 可以表示任意的类型,如果函数参数为 interface{},编译期间很难确定其参数的具体类型,也会发生逃逸。
package main
import "fmt"
func main() {
name := "devhui"
fmt.Println(name)
}
/* 逃逸分析日志
# command-line-arguments
./main.go:7:13: inlining call to fmt.Println
./main.go:7:13: name escapes to heap # 逃逸
./main.go:7:13: []interface {} literal does not escape
*/
闭包引用对象逃逸
package main
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
f()
}
}
func fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
/*
$ go build -gcflags=-m
# ceshi
.\main.go:11:9: can inline fibonacci.func1
.\main.go:10:2: moved to heap: a
.\main.go:10:5: moved to heap: b
.\main.go:11:9: func literal escapes to heap
*/
逃逸分析基本原则
编译器会根据变量是否被外部引用来决定是否逃逸:
如果函数外部没有引用,则优先放到栈中;
如果函数外部存在引用,则必定放到堆中;
如果栈上放不开,则必定放到堆上;
逃逸分析参数
go build -gcflags="-l -m" -o main main.go
# 删除文件
rm main
内存逃逸分析的好处
- 栈上分配内存比在堆中分配内存效率更高
- 栈上分配的内存不需要 GC 处理,而堆需要
- 逃逸分析目的是决定内分配地址是栈还是堆
- 逃逸分析在编译阶段完成