Ray-D-Song's Blog

Go 逃逸分析

2026-2-19 5min

什么是逃逸分析

逃逸分析(Escape Analysis) 是编译器在编译阶段执行的一种静态分析技术,它的核心目的是判断一个变量(或对象)会不会“逃逸”到堆上。

之所以要判断会不会逃逸到堆,主要是因为栈上分配内存非常快,函数返回就自动回收,不需要垃圾回收器介入。

堆上分配需要经过内存分配器 + 垃圾回收器管理,成本明显更高。

而编程语言之所以要区分堆还是栈,主要是因为栈大小必须在编译期(或函数开始时)确定,牺牲了灵活性。

由此我们也可以得出,一个对象会被分配到堆还是栈,由编译器在编译时决定。

决定的因素由很多,除了需要在编译期确定大小外,因为每个栈帧有大小限制,也不能存放太大的对象。

发生逃逸主要是以下 6 种场景:

场景是否逃逸原因
1对外部指针赋值逃逸&x 被赋给了全局变量、结构体字段、闭包捕获、map/slice元素等
2返回局部变量的指针逃逸return &localVar
3局部变量被闭包捕获,且闭包逃逸逃逸闭包本身逃逸 → 捕获的变量也要跟着逃逸
4动态大小的对象(如切片、map)几乎必逃切片底层数组、map 本身通常都要逃逸(除非极小且完全可内联的情况)
5interface{} 装箱逃逸任何值转成 interface{} 后几乎都会逃逸(因为接口里存的是指针)
6太大、无法确定大小的对象逃逸栈空间有限(通常几KB),编译器认为放栈上不安全时直接给堆

还有一点,容器类型的各个部分并不一定位于同一处,以切片为例,切片由三部分组成:指针 + 长度 + 容量,这三部分统称为 slice header,在 64 位系统上占用 24 个字节。slice header 和底层存储数据的数组,内存分配位置是分开考虑的。

部分常见情况能否栈分配?主要决定因素
slice header局部变量几乎总是可以除非 header 本身逃逸(返回 &s、给 interface{} 等)
底层数组(data)make 时长度/容量已知且较小可以栈分配编译期知道确切大小 + 不逃逸 + 不太大
底层数组(data)长度/容量运行时决定通常堆分配大小未知,编译器无法预留栈空间
底层数组(data)长度 ≥ ~64KB(8192 个 int 左右)强制堆分配即使不逃逸,栈太小(默认栈帧有限制)
底层数组(data)发生 append 扩容几乎必堆可能需要重新分配,更难留在栈上

为什么需要逃逸分析

逃逸分析主要用于分析和优化内存分配,尤其是搞清楚:

逃逸分析命令

go build -gcflags\="-m" .

命令的核心参数是:

常见的用法变体:

# 最常用写法(推荐加 all= 显示所有包的逃逸信息)
go build -gcflags="all=-m" .

# 只看当前包
go build -gcflags="-m" .

# 只看不编译(最干净,只看逃逸分析,不生成可执行文件)
go build -gcflags="all=-m" -n .

输出:

./main.go:12:6: x escapes to heap
./main.go:15:17: []int literal escapes to heap