翻译自: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是如何进行逃逸分析的会是一个不错的话题,可以参考这个链接,这个里面主要讲解了栈分配,以及有关何时可以消除同步的检测。