本篇文章主要介绍的是和go底层相关的并且非常重要的知识点,拿起小本本开始吧
Go 语言中的逃逸分析(Escape Analysis)是编译器在编译阶段进行的一项关键优化。它的核心目的是决定一个变量应该分配在栈(stack)上还是堆(heap)上,以提升程序性能。
🎯 核心目的:提升性能
栈分配:内存分配和释放的开销极小,速度非常快,由编译器自动管理。 堆分配:内存分配开销较大,且需要依赖垃圾回收器(GC)来回收,会给程序带来额外负担。
逃逸分析的目标就是尽可能将变量分配在栈上,从而减少堆内存分配的次数,降低 GC 压力,最终提升程序运行效率。
⚙️ 分析原理:变量是否“逃逸”
编译器会分析变量的作用域和生命周期。如果一个变量的生命周期超出了它被创建的函数作用域,就被认为发生了“逃逸”,必须分配到堆上。
简单来说,判断标准是:这个变量在函数返回后,是否还有可能被访问到?
是 → 发生逃逸,分配到堆。 否 → 未逃逸,分配到栈。
📝 常见的逃逸场景
根据分析,以下几种情况会导致变量逃逸到堆上:
这是最典型的逃逸场景。函数返回后,局部变量本应被销毁,但如果返回了它的地址,为了保证指针有效,编译器必须将其分配到堆上。
golangfunc newInt() *int {
x := 10 // x 是局部变量
return &x // 返回 x 的地址,x 逃逸到堆
}
接口变量在底层存储的是一个动态类型和一个动态值。当把一个具体类型的值赋给接口时,这个值需要被“装箱”,通常会分配到堆上。
golangfunc printValue() {
x := 100
var i interface{} = x // x 被赋值给接口 i,x 逃逸到堆
fmt.Println(i)
}
如果一个闭包(匿名函数)引用了其外部函数的局部变量,并且这个闭包的生命周期可能超过外部函数,那么被引用的变量就会逃逸到堆上。
golangfunc counter() func() int {
count := 0
// 匿名函数引用了外部变量 count
// 当 counter 函数返回后,匿名函数仍可能被调用,所以 count 必须存活
return func() int {
count++
return count
}
}
虽然 slice 或 map 的头部结构体可能分配在栈上,如果在make时,初始容量很大,这是会分配到堆上
golangpackage main
func main() {
s := make([]int, 100000000) // 底层数组会分配在堆上
s[0] = 100
}
查看逃逸分析
shellgo build -gcflags="-m" other/t1/main.go
# command-line-arguments
other/t1/main.go:3:6: can inline main
other/t1/main.go:4:11: make([]int, 100000000) escapes to heap
如果我们将容量该小一点
golangpackage main
func main() {
s := make([]int, 1000) // 底层数组会分配在堆上
s[0] = 100
}
查看逃逸分析
shellgo build -gcflags="-m" other/t1/main.go
# command-line-arguments
other/t1/main.go:3:6: can inline main
other/t1/main.go:4:11: make([]int, 1000) does not escape
这时就没有逃逸了


本文作者:繁星
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!