内存逃逸
是指编译器决定内存分配的位置,不需要程序员指定。
在函数中申请一个新的对象:
- 如果分配在栈中,则函数执行结束后可自动将内存回收;
- 如果分配在堆中,则函数结束后需要交给GC进行处理。
逃逸策略
在函数中申请新的对象时,编译器会根据该对象是否被函数外部引用来决定是否逃逸:
1. 函数外面没有引用,则优先放到栈中;
2. 函数外部存在引用,则必定放到堆中。
逃逸场景
1)指针逃逸
函数返回局部变量指针,函数内部定义的指针变量原本是在栈空间中开辟,但是函数将此局部变量返回出去了,那么指向该变量的内存地址是堆。
2)栈空间不足逃逸
当栈空间不足以存放当前对象或无法判断当前切片长度是会将对象分配到堆中。
3)动态类型逃逸
由于很多函数的类型是interface{} ,在编译期间很难确定其参数具体类型,也会产生逃逸
func main(){
s := "Escape"
fmt.Println(s)
}
变量s是一个string类型的变量,在调用fmt.Println()时发生了逃逸。
4)闭包引用对象逃逸
举一个斐波那契数列的例子
func Fib() func() int{
a, b := 0, 1
return func() int{
a, b = b, a+b
return a
}
}
func main(){
f := Fib()
for i:=0; i<10;i++{
fmt.Printf("Fib: %d\n", f())
}
}
闭包引用了函数的局部变量a、b,通过Fib函数获取闭包,每次执行闭包都会输出Fib数列。
而由于闭包中引用了变量a、b,所以导致两个变量的内存逃逸到了堆上。
小结
- 栈上分配内存比在堆中分配内存有更高的效率;
- 栈上分配的内存不需要GC;
- 堆上分配的内存使用完成之后交给GC处理;
- 逃逸分析的目的是决定分配地址是栈还是堆;
- 逃逸分析是在编译阶段完成。
question:函数传递指针真的比传值效率高吗
answer:传递指针可以减少底层值的复制,可以提高效率,但是如果复制的数据量小,由于指针传递会产生逃逸,则很有可能使用堆,从而加重GC的负担,所以传递指针不一定是高效的。