这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天
概念
在一段程序中,每一个函数都会有自己的内存区域存放自己的局部变量、返回地址等,这些内存会由编译器在栈中进行分配,每一个函数都会分配一个栈桢,在函数运行结束后进行销毁,但是有些变量我们想在函数运行结束后仍然使用它,那么就需要把这个变量在堆上分配,这种从"栈"上逃逸到"堆"上的现象就成为内存逃逸。
在栈上分配的地址,一般由系统申请和释放,不会有额外性能的开销,比如函数的入参、局部变量、返回值等。在堆上分配的内存,如果要回收掉,需要进行GC,那么GC一定会带来额外的性能开销。编程语言不断优化GC算法,主要目的都是为了减少GC带来的额外性能开销,变量一旦逃逸会导致性能开销变大。
逃逸机制
- 如果外部没有引用,则优先放到栈中;
- 如果外部存在引用,则一定放到堆中;
- 如果栈放不下,则一定放到堆中;
例子
指针逃逸
func esape1() *int{
var a int = 1
return &a
}
func main() {
escape1()
}
可以通过go build -gcflags=-m main.go查看逃逸情况
函数返回值为局部变量的指针, 函数虽然退出了,但是因为指针的存在,指向的内存不能随着函数的结束而回收,因此只能分配在堆上。
栈空间不足
func escape2() {
s := make([]int, 0, 10000)
for index,_ := range s{
s[index] = index
}
}
func main(){
escape2()
}
[]interface{}数据类型,通过[]赋值必定会出现逃逸。
package main
func main() {
data := []interface{}{100, 200}
data[0] = 100
}
map[string]interface{}类型尝试通过赋值,必定会出现逃逸。
package main
func main() {
data := make(map[string]interface{})
data["key"] = 200
}
[]*int数据类型,赋值的右值会发生逃逸现象。
package main
func main() {
a := 10
data := []*int{nil}
data[0] = &a
}
总结
- 栈上分配内存比堆中分配内存效率高
- 栈上分配的内存不需要GC处理,而堆需要
- 逃逸分析目的是决定内存分配地址是栈还是堆
- 逃逸分析在编译阶段完成