您好,登錄后才能下訂單哦!
本篇內容介紹了“Go語言中內存管理逃逸的方法是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
所謂的逃逸分析(Escape analysis)是指由編譯器決定內存分配的位置嗎不需要程序員指定。
函數中申請一個新的對象
如果分配在棧中, 則函數執行結束后可自動將內存回收
如果分配在堆中, 則函數執行借宿可交給GC(垃圾回收)處理
有了逃逸分析,返回函數局部變量將變得可能,除此之外,逃逸分析還跟閉包息息相關,了解哪些場景下對象會逃逸至關重要。
每當函數中申請新的對象,編譯器會根據該對象是否被函數外部引用來決定是否逃逸:
如果函數外部沒有引用,則優先放到棧中;
如果函數外部存在引用,則必定放到堆中;
注意,對于函數外部沒有引用的對象,也有可能放到堆中,比如內存過大超過棧的存儲能力。
我們知道Go可以返回局部變量指針,這其實是一個典型的變量逃逸案例,示例代碼如下:
package main type Student struct { Name string Age int } func StudentRegister(name string, age int) *Student { s := new(Student) //局部變量s逃逸到堆 s.Name = name s.Age = age return s } func main() { StudentRegister("Jim", 18) }
函數StudentRegister()內部s為局部變量,其值通過函數返回值返回,s本身為一指針,其指向的內存地址不會是棧而是堆,這就是典型的逃逸案例。
通過編譯參數-gcflag=-m可以查看編譯過程中的逃逸分析:
D:\SourceCode\GoExpert\src>go build -gcflags=-m
# _/D_/SourceCode/GoExpert/src
.\main.go:8: can inline StudentRegister
.\main.go:17: can inline main
.\main.go:18: inlining call to StudentRegister
.\main.go:8: leaking param: name
.\main.go:9: new(Student) escapes to heap
.\main.go:18: main new(Student) does not escape
可見在StudentRegister()函數中,也即代碼第9行顯示”escapes to heap”,代表該行內存分配發生了逃逸現象。
看下面的代碼,是否會產生逃逸呢?
package main func Slice() { s := make([]int, 1000, 1000) for index, _ := range s { s[index] = index } } func main() { Slice() }
上面代碼Slice()函數中分配了一個1000個長度的切片,是否逃逸取決于棧空間是否足夠大。
直接查看編譯提示,如下:
D:\SourceCode\GoExpert\src>go build -gcflags=-m
# _/D_/SourceCode/GoExpert/src
.\main.go:4: Slice make([]int, 1000, 1000) does not escape
我們發現此處并沒有發生逃逸。那么把切片長度擴大10倍即10000會如何呢?
D:\SourceCode\GoExpert\src>go build -gcflags=-m
# _/D_/SourceCode/GoExpert/src
.\main.go:4: make([]int, 10000, 10000) escapes to heap
我們發現當切片長度擴大到10000時就會逃逸。
實際上當棧空間不足以存放當前對象時或無法判斷當前切片長度時會將對象分配到堆中。
很多函數參數為interface類型,比如fmt.Println(a …interface{}),編譯期間很難確定其參數的具體類型,也會產生逃逸。
如下代碼所示:
package main import "fmt" func main() { s := "Escape" fmt.Println(s) }
上述代碼s變量只是一個string類型變量,調用fmt.Println()時會產生逃逸:
D:\SourceCode\GoExpert\src>go build -gcflags=-m
# _/D_/SourceCode/GoExpert/src
.\main.go:7: s escapes to heap
.\main.go:7: main ... argument does not escape
某著名的開源框架實現了某個返回Fibonacci數列的函數:
func Fibonacci() func() int { a, b := 0, 1 return func() int { a, b = b, a+b return a } }
該函數返回一個閉包,閉包引用了函數的局部變量a和b,使用時通過該函數獲取該閉包,然后每次執行閉包都會依次輸出Fibonacci數列。
完整的示例程序如下所示:
package main import "fmt" func Fibonacci() func() int { a, b := 0, 1 return func() int { a, b = b, a+b return a } } func main() { f := Fibonacci() for i := 0; i < 10; i++ { fmt.Printf("Fibonacci: %d\n", f()) } }
上述代碼通過Fibonacci()獲取一個閉包,每次執行閉包就會打印一個Fibonacci數值。輸出如下所示:
D:\SourceCode\GoExpert\src>src.exe
Fibonacci: 1
Fibonacci: 1
Fibonacci: 2
Fibonacci: 3
Fibonacci: 5
Fibonacci: 8
Fibonacci: 13
Fibonacci: 21
Fibonacci: 34
Fibonacci: 55
Fibonacci()函數中原本屬于局部變量的a和b由于閉包的引用,不得不將二者放到堆上,以致產生逃逸:
D:\SourceCode\GoExpert\src>go build -gcflags=-m
# _/D_/SourceCode/GoExpert/src
.\main.go:7: can inline Fibonacci.func1
.\main.go:7: func literal escapes to heap
.\main.go:7: func literal escapes to heap
.\main.go:8: &a escapes to heap
.\main.go:6: moved to heap: a
.\main.go:8: &b escapes to heap
.\main.go:6: moved to heap: b
.\main.go:17: f() escapes to heap
.\main.go:17: main ... argument does not escape
棧上分配內存比在堆中分配內存有更高的效率
棧上分配的內存不需要GC處理
堆上分配的內存使用完畢會交給GC處理
逃逸分析目的是決定內分配地址是棧還是堆
逃逸分析在編譯階段完成
思考一下這個問題:函數傳遞指針真的比傳值效率高嗎?
我們知道傳遞指針可以減少底層值的拷貝,可以提高效率,但是如果拷貝的數據量小,由于指針傳遞會產生逃逸,可能會使用堆,也可能會增加GC的負擔,所以傳遞指針不一定是高效的。
“Go語言中內存管理逃逸的方法是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。