【注意】最后更新于 January 10, 2020,文中内容可能已过时,请谨慎使用。
先提一个问题:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// escape.go
package main
import "fmt"
func intSeq() func() int {
i := 0
return func() int {
i++
return i
}
}
func main() {
nextInt := intSeq()
fmt.Println(nextInt())
fmt.Println(nextInt())
fmt.Println(nextInt())
}
|
会输出什么?
先简单介绍一下堆和栈
-
栈
在程序中,每个函数块都会有自己的内存区域用来存自己的局部变量(内存占用少)、返回地址、返回值之类的数据,这一块内存区域有特定的结构和寻址方式,大小在编译时已经确定,寻址起来也十分迅速,开销很少。这一块内存地址称为栈。栈是线程级别的,大小在创建的时候已经确定,所以当数据太大的时候,就会发生”stack overflow”。
-
堆
在程序中,全局变量、内存占用大的局部变量、发生了逃逸的局部变量存在的地方就是堆,这一块内存没有特定的结构,也没有固定的大小,可以根据需要进行调整。简单来说,有大量数据要存的时候,就存在堆里面。堆是进程级别的。当一个变量需要分配在堆上的时候,开销会比较大,对于 go 这种带 GC 的语言来说,也会增加 gc 压力,同时也容易造成内存碎片。
通常来讲, 分配在栈上的内存, 在函数执行完毕后就会被回收, 堆上的会根据gc算法来回收
再回到最开始的问题上, 上面的代码会输出:
中间发生了什么, 我们一步一步的讲
首先执行 go build -gcflags '-m' escape.go
, 输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# command-line-arguments
./escape.go:7:9: can inline intSeq.func1
./escape.go:14:13: inlining call to fmt.Println
./escape.go:15:13: inlining call to fmt.Println
./escape.go:16:13: inlining call to fmt.Println
./escape.go:6:2: moved to heap: i
./escape.go:7:9: func literal escapes to heap
./escape.go:14:21: nextInt() escapes to heap
./escape.go:14:13: main []interface {} literal does not escape
./escape.go:14:13: io.Writer(os.Stdout) escapes to heap
./escape.go:15:21: nextInt() escapes to heap
./escape.go:15:13: main []interface {} literal does not escape
./escape.go:15:13: io.Writer(os.Stdout) escapes to heap
./escape.go:16:21: nextInt() escapes to heap
./escape.go:16:13: main []interface {} literal does not escape
./escape.go:16:13: io.Writer(os.Stdout) escapes to heap
<autogenerated>:1: (*File).close .this does not escape
|
像 can inline
, inlining call to
这种内联的先不管
注意这一条 ./escape.go:6:2: moved to heap: i
编译的时候把i
变量的内存地址放到了堆(heap)上
因为当 ./escape.go:7:
时, 函数 intSeq
返回一个闭包, 然而这个函数在这时还没执行, 因此内存不能回收, 而闭包中的变量 i
本来是 intSeq
函数内声明的一个局部变量,这么随着闭包返出的时候, 自然要从栈内存分配到堆内存上, 而且这个闭包发生了内存逃逸 nextInt() escapes to heap
, 因此 i
变成了一个范围更大的一个局部变量, 被包在nextInt()
函数中, 因此只要 nextInt
这个变量不被销毁, 那么 i
就一直存在着, 相当于无数个 nextInt
专属的 “全局变量”
相关资料
文章作者
GPF
上次更新
2020-01-10
(8804c68)