什么是逃逸
内存从栈逃逸到堆中,会增大GC的压力
为什么要做逃逸分析
申请到栈内存性能好,不会引起GC,函数返回直接释放
申请到堆内存会导致gc,引起性能问题
如何分配:
- 如果函数外部没有引用,则优先放到栈中;
- 如果函数外部存在引用,则必定放到堆中;
常见的三种逃逸
指针逃逸
函数返回局部变量的指针导致指针逃逸
package main
func pointerEscapeFunc() *int{
var v int
return &v
}
func main(){
pointerEscapeFunc()//v会被分配到堆上
return
}
输出:
host$ go build -gcflags '-m -l' tem.go #-m打印信息,-l忽略inline信息
# command-line-arguments
./tem.go:3:6: moved to heap: v #可见v被分配到了堆上
栈空间不足
go的goroutine初始栈大小为2KB,go可以增大栈大小,但不可超过系统栈限制(使用ulimit -s
查看),超过一定大小的变量将会逃逸到堆上,不同go版本大小限制不同
package main
func stackSpaceExhausted(){
v := make([]int,0,10000)
}
func main(){
stackSpaceExhausted()
}
输出:
host$ go build -gcflags '-m -l' tem.go
# command-line-arguments
./tem.go:3:11: make([]int, 0, 10000) escapes to heap # 可见逃逸到了堆上,go1.15
闭包引用
函数类型也分两种,一种是函数字面量类型(未命名类型,func literal),另一种是函数命名类型。
package main
func outer() func() int{
var a int
return func() int{
a++
return a
}
}
func main(){
inner := outer()
print(inner())
}
输出:
host$ go build -gcflags '-m -l' tem.go
# command-line-arguments
./tem.go:3:6: moved to heap: a
./tem.go:4:9: func literal escapes to heap
动态类型
对象大小不确定或作为不确定大小的参数时发生逃逸
package main
import "fmt"
func main(){
var a int
fmt.Println(a)//fmt.Println的入参是...interface{}
}
输出:
host$ go build -gcflags '-m -l' tem.go
# command-line-arguments
./tem.go:7:13: main ... argument does not escape
./tem.go:7:13: a escapes to heap
逃逸分析使用
传值会拷贝对象,增加对象拷贝开销(听君一席话,胜似一席话),只读切内存小的结构体使用可以提高性能
传指针会导致内存逃逸到堆中,增加垃圾回收(GC)负担,对象需要频繁创建删除时,GC开销会特别大,影响性能,但在需要修改对象值、内存占用大的结构体中,传指针性能更好