1.函数返回局部指针变量
func Add(x,y int) *int { res := 0 res = x + y return &res } func main() { Add(1,2) }
函数返回局部变量是一个指针变量,该函数执行结束,对应栈帧就会销毁,但是引用返回到函数外部,如果外部解析地址,就会导致程序访问非法内存,所以经过编辑器分析过后将其在堆上分配
2.interface类型逃逸
1)interface产生逃逸
func main() { str := "荔枝" fmt.Println(str) }
str是main的一个局部变量,传给 fmt.Printl()
之后逃逸,因为fmt.Println()
的入参是interface{}
类型,那么编译期间就很难确定参数类型
2)指向栈对象的指针不能在堆中
func main() { str := "苏珊" fmt.Println(&str) }
这次str也逃逸到堆上面了,在堆上面进行分配,因为入参是interface,变量str
的地址被以实参的方式传入fmt.Println
被装箱到一个interface{}
装箱的形参变量要在堆上分配,但是还需要存储一个栈上的地址,这和之前说的第一条不符,所以str也会分配到堆上
3.闭包产生逃逸
func Increase() func() int { n := 0 return func() int { n++ return n } } func main() { in := Increase() fmt.Println(in()) // 1 }
因为函数是指针类型,所以匿名函数当做返回值产生逃逸,匿名函数使用外部变量n
,这个n
会一直存在知道in
被销毁
4. 变量大小不确定及栈空间不足引发逃逸
import ( "math/rand" ) func LessThan8192() { nums := make([]int, 100) // = 64KB for i := 0; i < len(nums); i++ { nums[i] = rand.Int() } } func MoreThan8192(){ nums := make([]int, 1000000) // = 64KB for i := 0; i < len(nums); i++ { nums[i] = rand.Int() } } func NonConstant() { number := 10 s := make([]int, number) for i := 0; i < len(s); i++ { s[i] = i } } func main() { NonConstant() MoreThan8192() LessThan8192() }
栈空间足够不会发生逃逸,但是变量过大,已经超过栈空间,会逃逸到堆上
总结
1)逃逸分析在编译阶段确定哪些变量可以分配在栈中,哪些变量分配在堆上
2)逃逸分析减轻了GC压力,提高程序的运行速度
3)栈上内存使用完毕不需要GC处理,堆上内存使用完毕会交给GC处理
4)函数传参时对于需要修改原对象值,或占用内存比较大的结构体,选择传指针;对于只读的占用内存较小的结构体,直接传值能够获得更好的性能
5)根据代码具体分析,尽量减少逃逸代码,减轻GC压力,提高性能