先提一个问题:

 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算法来回收

再回到最开始的问题上, 上面的代码会输出:

1
2
3
1
2
3

中间发生了什么, 我们一步一步的讲

首先执行 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 专属的 “全局变量”

相关资料