go build -gcflags="-l -m" -o main main.go
# 删除文件
rm main
重点:
// go 语言内存逃逸分析
// 1. 分配在栈上的空间不会逃逸, 函数结束后回收
// 2. 堆上分配的内存使用完毕之后交给 GC 处理
// 3. 逃逸分析在编译阶段完成, 目的是决定对象分配到**堆**还是**栈**
闭包造成的逃逸
原本在函数运行栈空间上分配的内存,由于闭包的关系,变量在函数的作用域之外使用
package main
func closure() func() int {
var a int
return func() int {
a++
return a
}
}
返回指向栈变量的指针
返回的变量是栈对象的指针,编译器认为该对象在函数结束之后还需要使用
package main
type Empty struct {}
func Demo() *Empty {
return &Empty{}
}
func main() {
}
申请大对象造成的逃逸
申请的空间过大, 也会直接在堆上分配空间
package main
func bigObj() []int {
return make([]int, 10000+1)
}
func main() {
_ = bigObj()
}
申请可变长空间造成的逃逸
编译器无法知道需要分配多大的空间, 由于 ln 是可变的变量
package main
func allocateVar(ln int) []int {
return make([]int, ln)
}
func main() {
}
返回局部引用的 slice 造成的逃逸
编译器认为返回的对象可能会在函数调用完成之后, 还会再次使用 所以, 编译器会在堆上分配内存空间
package main
func returnSlice() [] int {
return make([]int,10)
}
func main() {
_ = make(map[string]struct{})
}
返回局部引用的 map 造成的逃逸
编译器认为返回的对象可能会在函数调用完成之后, 还会再次使用 所以, 编译器会在堆上分配内存空间
package main
func returnMap() map[string] struct {} {
return make(map[string]struct{})
}
func main() {
}
返回函数造成的逃逸
package main
import "fmt"
func escapeFunction() func() func() {
return func() func() {
return func() {
fmt.Println("hello closure function")
}
}
}
var (
_ = escapeFunction
)
func main() {
}
总结
- 闭包造成的逃逸
- 返回指向栈变量的指针
- 申请大对象造成的逃逸
- 申请可变长空间造成的逃逸
- 返回局部引用的 slice 造成的逃逸
- 返回局部引用的 map 造成的逃逸
- 返回函数造成的逃逸
- 逃逸是在编译期间完成的,主要是决定是在栈中或者堆中分配内存 源代码 🦀