栈内存不足逃逸到堆内存,但是到底达到多大的内存时才会发生逃逸呢?

结论:当栈内单个对象大小超过64KB,则会发生内存逃逸

栈内存不足逃逸到堆内存的场景分析如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
type student struct {
name string // 16byte
}

func stackSpace() {
// 动态大小,发生逃逸
length := 10
space1 := make([]int, length)
for i := 0; i < len(space1); i++ {
space1[i] = i
}
len1 := unsafe.Sizeof(space1)
fmt.Println("space1 length:", len1)

// 未发生逃逸
space2 := make([]int, 100, 100)
for i := 0; i < len(space2); i++ {
space2[i] = i
}
len2 := unsafe.Sizeof(space2)
fmt.Println("space2 length:", len2)

// 当int32整型数组容量大于8192*2时(64KB,单个int32大小为4字节),发生逃逸
space3 := make([]int32, 8192*2+1, 8192*2+1)
for i := 0; i < len(space3); i++ {
space3[i] = int32(i)
}
len3 := unsafe.Sizeof(space3)
fmt.Println("space3 length:", len3)

// 当int64整型数组容量大于8192时(64KB,单个int64大小为8字节),发生逃逸
space4 := make([]int64, 8193, 8193)
for i := 0; i < len(space4); i++ {
space4[i] = int64(i)
}
len4 := unsafe.Sizeof(space4)
fmt.Println("space4 length:", len4)

// 当字符串指针数组容量大于4096时(64KB,单个字符串大小为16字节),发生逃逸
space5 := make([]string, 4097, 4097)
for i := 0; i < len(space5); i++ {
s := "a"
space5[i] = s
}
len5 := unsafe.Sizeof(space5)
fmt.Println("space5 length:", len5)

// 当字符串指针数组容量大于8192时(64KB,单个指针大小为8字节),发生逃逸,变量s也发生逃逸
space6 := make([]*string, 8193, 8193)
for i := 0; i < len(space6); i++ {
s := "abc"
space6[i] = &s
}
len6 := unsafe.Sizeof(space6)
fmt.Println("space6 length:", len6)

// 当自定义对象数组容量大于4096时(64KB,单个对象大小为16字节),发生逃逸
space7 := make([]student, 4097, 4097)
for i := 0; i < len(space7); i++ {
space7[i].name = "abc"
}
len7 := unsafe.Sizeof(space7)
fmt.Println("space7 length:", len7)
}

对单个文件进行逃逸分析:

1
go build -gcflags='-m -l' memory_analysis.go

逃逸分析结果,可以直接看make语句日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# command-line-arguments
./memory_analysis.go:67:9: moved to heap: s
./memory_analysis.go:24:19: make([]int, length) escapes to heap
./memory_analysis.go:29:16: ... argument does not escape
./memory_analysis.go:29:17: "space1 length:" escapes to heap
./memory_analysis.go:29:17: len1 escapes to heap
./memory_analysis.go:32:19: make([]int, 100, 100) does not escape
./memory_analysis.go:37:16: ... argument does not escape
./memory_analysis.go:37:17: "space2 length:" escapes to heap
./memory_analysis.go:37:17: len2 escapes to heap
./memory_analysis.go:40:19: make([]int32, 8192 * 2 + 1, 8192 * 2 + 1) escapes to heap
./memory_analysis.go:45:16: ... argument does not escape
./memory_analysis.go:45:17: "space3 length:" escapes to heap
./memory_analysis.go:45:17: len3 escapes to heap
./memory_analysis.go:48:19: make([]int64, 8193, 8193) escapes to heap
./memory_analysis.go:53:16: ... argument does not escape
./memory_analysis.go:53:17: "space4 length:" escapes to heap
./memory_analysis.go:53:17: len4 escapes to heap
./memory_analysis.go:56:19: make([]string, 4097, 4097) escapes to heap
./memory_analysis.go:62:16: ... argument does not escape
./memory_analysis.go:62:17: "space5 length:" escapes to heap
./memory_analysis.go:62:17: len5 escapes to heap
./memory_analysis.go:65:19: make([]*string, 8193, 8193) escapes to heap
./memory_analysis.go:71:16: ... argument does not escape
./memory_analysis.go:71:17: "space6 length:" escapes to heap
./memory_analysis.go:71:17: len6 escapes to heap
./memory_analysis.go:74:19: make([]student, 4097, 4097) escapes to heap
./memory_analysis.go:79:16: ... argument does not escape
./memory_analysis.go:79:17: "space7 length:" escapes to heap
./memory_analysis.go:79:17: len7 escapes to heap