問題
知道golang的內(nèi)存逃逸嗎暂殖?什么情況下會發(fā)生內(nèi)存逃逸?
怎么答
golang程序變量
會攜帶有一組校驗數(shù)據(jù)当纱,用來證明它的整個生命周期是否在運行時完全可知呛每。如果變量通過了這些校驗,它就可以在棧上
分配坡氯。否則就說它 逃逸
了晨横,必須在堆上分配
。
能引起變量逃逸到堆上的典型情況:
- 在方法內(nèi)把局部變量指針返回 局部變量原本應(yīng)該在棧中分配箫柳,在棧中回收手形。但是由于返回時被外部引用,因此其生命周期大于棧滞时,則溢出叁幢。
- 發(fā)送指針或帶有指針的值到 channel 中。 在編譯時坪稽,是沒有辦法知道哪個 goroutine 會在 channel 上接收數(shù)據(jù)曼玩。所以編譯器沒法知道變量什么時候才會被釋放。
- 在一個切片上存儲指針或帶指針的值窒百。 一個典型的例子就是 []*string 黍判。這會導(dǎo)致切片的內(nèi)容逃逸。盡管其后面的數(shù)組可能是在棧上分配的篙梢,但其引用的值一定是在堆上顷帖。
- slice 的背后數(shù)組被重新分配了,因為 append 時可能會超出其容量( cap )渤滞。 slice 初始化的地方在編譯時是可以知道的贬墩,它最開始會在棧上分配。如果切片背后的存儲要基于運行時的數(shù)據(jù)進行擴充妄呕,就會在堆上分配陶舞。
- 在 interface 類型上調(diào)用方法。 在 interface 類型上調(diào)用方法都是動態(tài)調(diào)度的 —— 方法的真正實現(xiàn)只能在運行時知道绪励。想像一個 io.Reader 類型的變量 r , 調(diào)用 r.Read(b) 會使得 r 的值和切片b 的背后存儲都逃逸掉肿孵,所以會在堆上分配。
舉例
- 通過一個例子加深理解疏魏,接下來嘗試下怎么通過
go build -gcflags=-m
查看逃逸的情況停做。
package main
import "fmt"
type A struct {
s string
}
// 這是上面提到的 "在方法內(nèi)把局部變量指針返回" 的情況
func foo(s string) *A {
a := new(A)
a.s = s
return a //返回局部變量a,在C語言中妥妥野指針,但在go則ok大莫,但a會逃逸到堆
}
func main() {
a := foo("hello")
b := a.s + " world"
c := b + "!"
fmt.Println(c)
}
執(zhí)行go build -gcflags=-m main.go
go build -gcflags=-m main.go
# command-line-arguments
./main.go:7:6: can inline foo
./main.go:13:10: inlining call to foo
./main.go:16:13: inlining call to fmt.Println
/var/folders/45/qx9lfw2s2zzgvhzg3mtzkwzc0000gn/T/go-build409982591/b001/_gomod_.go:6:6: can inline init.0
./main.go:7:10: leaking param: s
./main.go:8:10: new(A) escapes to heap
./main.go:16:13: io.Writer(os.Stdout) escapes to heap
./main.go:16:13: c escapes to heap
./main.go:15:9: b + "!" escapes to heap
./main.go:13:10: main new(A) does not escape
./main.go:14:11: main a.s + " world" does not escape
./main.go:16:13: main []interface {} literal does not escape
<autogenerated>:1: os.(*File).close .this does not escape
./main.go:8:10: new(A) escapes to heap
說明new(A)
逃逸了,符合上述提到的常見情況中的第一種蛉腌。./main.go:14:11: main a.s + " world" does not escape
說明b
變量沒有逃逸,因為它只在方法內(nèi)存在,會在方法結(jié)束時被回收眉抬。./main.go:15:9: b + "!" escapes to heap
說明c
變量逃逸贯吓,通過fmt.Println(a ...interface{})
打印的變量,都會發(fā)生逃逸蜀变,感興趣的朋友可以去查查為什么悄谐。以上操作其實就叫逃逸分析。下篇文章库北,跟大家聊聊怎么用一個比較trick的方法使變量不逃逸爬舰。方便大家在面試官面前秀一波。