一架梯子,一头程序猿,仰望星空!
Golang高级特性面试题 > 内容正文

Golang 什么是逃逸分析?


问题简答

Go 编译器用于判断某个变量需要分配在栈上,还是堆上机制,就称之为逃逸分析(escape analysis),逃逸分析由编译器完成,作用于编译阶段。

问题详解:

要理解Go的逃逸分析,需要先理解内存中存储变量的两个区域:堆和栈。

堆和栈

在Go中,栈的内存是由编译器自动进行分配和释放,栈区往往存储着函数参数、局部变量和调用函数帧,它们随着函数的创建而分配,函数的退出而销毁。一个goroutine对应一个栈,栈是调用栈(call stack)的简称。一个栈通常又包含了许多栈帧(stack frame),它描述的是函数之间的调用关系,每一帧对应一次尚未返回的函数调用,它本身也是以栈形式存放数据。

说明:简单的说,函数参数、局部变量和调用函数帧这些数据都是保存在栈内存里面,函数返回了就释放掉内存,数据就没了。

Go程序在运行时只会存在一个堆,程序在运行期间可以主动从堆上申请内存,这些内存通过Go的内存分配器分配,并由垃圾收集器回收。

说明:相对于栈内存,堆内存中存储的数据生命周期更长,由Go的垃圾收集器负责回收。

Go逃逸分析

通过前面我们知道栈和堆的含义,那么Go怎么知道一个变量的数据应该放在栈内存空间,还是堆内存空间?

Go 编译器用于判断某个变量需要分配在栈上,还是堆上机制,就称之为逃逸分析(escape analysis),逃逸分析由编译器完成,作用于编译阶段。

提示:Go中说变量a逃逸,通常指的是变量a逃逸到了堆内存中。

前面提到栈空间的时候,提到函数的参数、局部变量都保存在栈空间,为什么还需要逃逸分析,不是已经知道那些变量应该在栈内存中分配? 上面提到的只是一般情况,下面是会发生变量逃逸到堆内存的情况

1、指针逃逸分析例子

函数虽然退出了,但是因为指针的存在,对象的内存不能随着函数结束而释放,因此只能分配在堆上。

package main

import "fmt"

type User struct {
    name string
}

func createUser(name string) *User {
    u := new(User) // 局部变量 u 逃逸到堆
    u.name = name
    return u
}

func main() {
    u := createUser("tizi365")
    fmt.Println(u)
}

通过下面命令对go程序进行逃逸分析

// 设置go build的参数 -gcflags=-m 
go build -gcflags=-m taoyi.go

输出

# command-line-arguments
./taoyi.go:9:6: can inline createUser
./taoyi.go:16:17: inlining call to createUser
./taoyi.go:17:13: inlining call to fmt.Println
./taoyi.go:9:17: leaking param: name
./taoyi.go:10:10: new(User) escapes to heap
./taoyi.go:16:17: new(User) escapes to heap
./taoyi.go:17:13: ... argument does not escape

new(User) escapes to heap 就代表创建出来的变量逃逸到了堆上。

2、其他会发生逃逸的的场景

  • 变量类型不确定
  • 变量所占内存较大
  • 变量大小不确定
  • 栈空间不足