翻译自:http://www.agardner.me/golang/garbage/collection/gc/escape/analysis/2015/10/18/go-escape-analysis.html
垃圾回收是Go的一个很方便的特性--其自动的内存管理使代码更整洁,同时减少内存泄漏的可能性。但是,由于垃圾回收需要周期性的停止程序从而去收集不用的对象,不可避免的会增加额外开销。Go编译器是智能的,它会自动决定一个变量是应该分配在堆上从而在将来便于回收,还是直接分配到函数的栈空间。对于分配到栈上的变量,其与分配到堆上的变量不同之处在于:随着函数的返回,栈空间会被销毁,从而栈上的变量被直接销毁,不需要额外的垃圾回收开销。
Go的逃逸分析相对于Java虚拟机的HotSpot来说更为基础。基本规则就是,如果一个变量的引用从声明它的函数中返出去了,则发生“逃逸”,因为它有可能在函数外被别的内容使用,所以必须分配到堆上。如下几种情况会比较复杂:
- 函数调用其他函数
- 引用作为结构体的成员变量
- 切片和映射
- Cgo指向变量的指针
-gcflags '-m' package main
type S struct {}
func main() {
var x S
_ = identity(x)
}
func identity(x S) S {
return x
} go run -gcflags '-m -l'identitymainxidentity package main
type S struct {}
func main() {
var x S
y := &x
_ = *identity(y)
}
func identity(z *S) *S {
return z
} 其对应的输出是:
./escape.go:11: leaking param: z to result ~r1 ./escape.go:7: main &x does not escape
zidentityzmainxxmain package main
type S struct {}
func main() {
var x S
_ = *ref(x)
}
func ref(z S) *S {
return &z
} 其输出为:
./escape.go:10: moved to heap: z ./escape.go:11: &z escapes to heap
zxrefzzrefrefmainmainref package main
type S struct {
M *int
}
func main() {
var i int
refStruct(i)
}
func refStruct(y int) (z S) {
z.M = &y
return z
} 其输出为:
./escape.go:12: moved to heap: y ./escape.go:13: &y escapes to heap
refStructy package main
type S struct {
M *int
}
func main() {
var i int
refStruct(&i)
}
func refStruct(y *int) (z S) {
z.M = y
return z
} 其输出为:
./escape.go:12: leaking param: y to result z ./escape.go:9: main &i does not escape
mainirefStructimainrefStructi 再来看一个有点弯弯绕的例子:
package main
type S struct {
M *int
}
func main() {
var x S
var i int
ref(&i, &x)
}
func ref(y *int, z *S) {
z.M = y
} 其输出为:
./escape.go:13: leaking param: y ./escape.go:13: ref z does not escape ./escape.go:9: moved to heap: i ./escape.go:10: &i escapes to heap ./escape.go:10: main &x does not escape
y slicereflectmap[10000]int 如果你剖析过你的程序堆使用情况(https://blog.golang.org/pprof
),并且想减少垃圾回收的消耗,可以将频繁分配到堆上的变量移到栈上,可能会有较好的效果。进一步研究HotSpot JVM是如何进行逃逸分析的会是一个不错的话题,可以参考这个链接,这个里面主要讲解了栈分配,以及有关何时可以消除同步的检测。