说明
逃逸分析是指由编译器决定分配的位置,是在栈还中堆上,不需要程序员指定。
函数中申请一个新的对象,分配的内存可能产生如下情况
- 如果分配在栈中,则在执行结束时可以自动回收
- 如果分配在堆中,则在执行结束后,由GC处理回收,GC并不会立即回收,其有相关的回收策略
如果要查看程序是的逃逸
情况,可以在编译的时候添加参数--gcflags=-m
, 查看输入里面是否有escapes to heap
的输入,有则是发生逃逸
逃逸策略
- 如果函数外部没有引用,则优先分配到栈中,这里是优先则是因为栈内存不够的时候,就会放到堆中
- 如果函数外部有引用,则一定分配到堆中
逃逸场景
指针逃逸
最典型的逃逸的案例,在定义结构体的时候,一般都会使用类似NewClient 这样的形式进行构建一个对象返回,如下:
package main
type Student struct {
Name string
Age int
}
func StudentRegister(name string, age int) *Student {
s := new(Student) // 局部变量s逃逸到堆
s.Name = name
s.Age = age
return s
}
func main() {
StudentRegister("Jim", 18)
}
说明:StudentRegister
函数中的变量s为其局部变量,但其通过函数返回指针的形式,返回了其指针地址,其指向的就是堆,而不是栈
栈空间不足时,也会发生逃逸
如下代码:
package main
func appendSclice() {
// 此处申请的空间,在栈中还是够的,所以不会逃逸
s := make([]int, 1000, 1000)
// 当把变量s的长度扩大10或更大,就会发生逃逸,如: s := make([]int, 10000, 1000)
// 这就是因为栈空间不足,导致逃逸,会把内存分配到堆上
for index:=range s {
s[index] = index
}
}
func main () {
appendSclice()
}
动态类型【interface{}】逃逸
最简单的动态类型逃逸,如下代码:
package main
func main() {
s := "ken"
fmt.Println("%v", s)
}
闭包引用的逃逸
经典的 Fibonacci数列算法函数,就是闭包逃逸
func Fibonacci () func () int {
a, b := 0,1 //函数中原本属于局部变量的a和b由于闭包的引用,不得不将二者放到堆上,以致产生逃逸
return func () int {
a, b = b, a+b
return a
}
}
总结
- 栈上分配的内存比在堆上要效率要高
- 栈上分配的内存不需要GC处理,在函数执行完成后,就可以直接回收
- 堆上分配的内存,一定会交给GC处理
- 逃逸分析的目的是为了确定内存分配的地址是栈上还是堆上
- 逃逸分析在编译阶段完成
提示
函数的参数,传递指针一定比传值效率高么?
传递指针时,可以减少低层的值拷贝操作,以提交效率,这在数据量比较大的时候,可以考虑,
但是要明白,传递指针是会产生逃逸的,会把变量分配到堆上,而分配到堆上的变量,又是只能通过GC来进来回收,GC在执行时,几乎会暂停所有内存分配操作的,所以在变量比较小的时候,如果传递指针做为参数,反而会加重GC的负担,效率并不会提交