golang內存逃逸的學習筆記
逃逸分析( Escape analysis) 是指由編譯器決定內存分配的位置, 不需要程序員指定。 函數中申請一個新的對象。
- 如果分配在棧中, 則函數執(zhí)行結束可自動將內存回收;
- 如果分配在堆中, 則函數執(zhí)行結束可交給GC( 垃圾回收) 處理;
內存逃逸策略
每當函數中申請新的對象, 編譯器會跟據該對象是否被函數外部引用來決定是否逃逸:
- 如果函數外部沒有引用, 則優(yōu)先放到棧中
- 如果函數外部存在引用, 則必定放到堆中;
注意, 對于函數外部沒有引用的對象, 也有可能放到堆中, 比如內存過大超過棧的存儲能力。
逃逸場景分析
指針逃逸
Go可以返回局部變量指針, 這其實是一個典型的變量逃逸案例, 示例代碼如下:
package main
type Student struct {
Name string
age int
}
func NewStudent(name string, age int) *Student {
stu := new(Student)
stu.Name = name
stu.age = age
return stu
}
func main() {
NewStudent("abcd", 24)
}
函數NewStudent()內部stu為局部變量, 其值通過函數返回值返回, stu本身為一指針, 其指向的內存地址不會是棧而是堆, 這就是典型的逃逸案例。
通過編譯參數-gcflags=-m可以查年編譯過程中的逃逸分析:
運行結果: 請注意運行結果中出現(xiàn)了escapes to heap,也就是發(fā)生了內存逃逸現(xiàn)象。

??臻g不足逃逸
分析一下下面代碼會不會產生內存逃逸現(xiàn)象
package main
type Student struct {
Name string
age int
}
func Slice() {
s := make([]int, 1000, 1000)
for index, _ := range s {
s[index] = index
}
}
func main() {
Slice()
}
上面代碼Slice()函數中分配了一個1000個長度的切片, 是否逃逸取決于??臻g是否足夠大。 直接查看編譯提示, 如下可見并沒有發(fā)生逃逸:

但是如果長度擴大10倍呢?那情況會怎么樣?
package main
func Slice() {
s := make([]int, 10000, 10000)
for index, _ := range s {
s[index] = index
}
}
func main() {
Slice()
}
結果:

我們發(fā)現(xiàn)當切片長度擴大到10000時就會逃逸。實際上當棧空間不足以存放當前對象時或無法判斷當前切片長度時會將對象分配到堆中
動態(tài)類型逃逸
很多函數參數為interface類型, 比如fmt.Println(a …interface{}), 編譯期間很難確定其參數的具體類型,也人產生逃逸。 如下代碼所示:
package main
import "fmt"
func main() {
s := "abcd"
fmt.Println(s)
}
結果:上述代碼s變量只是一個string類型變量, 調用fmt.Println()時會產生逃逸。

閉包引用對象逃逸
相信刷題的同學對這代碼是十分的熟悉:
package main
import "fmt"
func Fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a + b
return a
}
}
func main() {
res := Fibonacci()
for i := 0; i < 10; i++ {
fmt.Printf("Print Result is %d\n" , res())
}
}
這段代碼的運行結果如下:

但是Fibonacci()函數中原本屬于局部變量的a和b由于閉包的引用, 不得不將二者放到堆上, 以致產生逃逸:

總結
- 棧上分配內存比在堆中分配內存有更高的效率
- 棧上分配的內存不需要GC處理
- 堆上分配的內存使用完畢會交給GC處理
- 逃逸分析目的是決定內分配地址是棧還是堆
- 逃逸分析在編譯階段完成
到此這篇關于golang內存逃逸的學習筆記的文章就介紹到這了,更多相關Golang內存逃逸 內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

