Go中的堆栈与逃逸分析

Go的变量到底是分配在堆还是栈,我们先聊下下概念。

  • 堆(heap) 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
  • 栈 (stack) 由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。Go在分配栈的时候,预先分配2k的空间。

理论上分配在栈上的数据操作速度快与堆上。堆上使用的内存需要垃圾回收(GC),栈上的内存是自动释放的且是明确不共享的。

先看个例子 escape.go :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

type user struct {
name string
email string
}

func main() {

newUser()
}

//go:noinline
func newUser() user {
u := user{
name: "tom",
email: "[email protected]",
}
return u
}

编译分析

1
2
~ go tool compile -m escape.go
escape.go:8:6: can inline main

这里只说明main可以内联,此时u是分配到栈上的,因为没有其他程序共享u变量。继续修改下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

type user struct {
name string
email string
}

func main() {

newUser()
}

//go:noinline
func newUser() *user {
u := user{
name: "tom",
email: "[email protected]",
}
return &u
}

结果

1
2
3
4
~ go tool compile -m escape.go
escape.go:8:6: can inline main
escape.go:18:6: can inline changeName
escape.go:18:17: moved to heap: u

我们返回user的指针,Go判断这段内存需要被共享于是本身u在堆上的被移动到了栈了。如果我们修改

1
2
3
4
5
6
7
func newUser() *user {
u := &user{
name: "tom",
email: "[email protected]",
}
return u
}
1
2
3
~ go tool compile -m escape.go
escape.go:8:6: can inline main
escape.go:17:10: &user literal escapes to heap

Go直接将这段内存分配到堆上了。一旦分配在堆上就要进行垃圾回收处理了。这里要注意的每个Goroutine都有自己的栈内存互不影响,Goroutine之间栈内存能不能共享!