浅谈Golang逃逸分析

什么是内存逃逸

通常情况下,函数里的局部变量 会被分配到 内存空间上,当函数执行结束,这些局部变量所占用的内存会被自动释放。然而,若变量的生命周期超出了函数的作用范围,编译器会将该变量分配到堆内存上,这种现象就叫做内存逃逸。

什么情况下会发生内存逃逸

逃逸分析是一种静态代码分析技术,Go 编译器在编译时会对代码进行数据流和控制流分析,以确定变量的生命周期和作用域,其核心目标是判断变量是否会在函数外部被引用,如果会,则该变量需要被分配到堆上,发生内存逃逸;如果不会,则可以安全地分配到栈上。

Golang程序变量会携带有一组校验数据,用来证明它的整个生命周期和作用域是否在运行时完全可知。如果变量通过了这些校验,它就可以在栈上分配。否则就说它 逃逸 了,必须在堆上分配

可以使用该命令辅助分析,go build -gcflags="-m -l" ***.go

典型的会引起内存逃逸的情况:

  1. 函数将局部变量的指针返回:局部变量原本应该在栈中分配,在栈中回收。因为函数执行结束后,栈上的局部变量会被销毁,如果返回其指针,外部代码就无法正常访问该变量,所以编译器会将其分配到堆上。
go 复制代码
func test() *int {
    num := 10
    return &num
}

func main() {
    fmt.Println(test())
}
  1. 发送指针变量或包含指针的值到channel中:像结构体、切片、映射等数据类型,若其内部包含指针类型的字段;或者直接将一个变量的指针发送到channel中,在编译时,没有办法知道哪个 goroutine 会接收该数据。无法保证接收方在函数结束后还会不会通过该指针访问变量,所以编译器会把变量分配到堆上。
go 复制代码
func test2(ch chan *int) {
    a := 1
    ch <- &a
}

func main() {
    ch := make(chan *int)
    go test2(ch)
    <-ch
}

3. 切片发生扩容,并且作为返回值或闭包使用 :其实就算不发生扩容,把一个切片作为函数返回值,或者闭包使用,也会发生逃逸,只不过强调扩容是因为会使切片的内存管理更加复杂,内存逃逸的现象更加明显,对程序的性能和内存使用可能产生更大的影响,需要开发者更加关注和谨慎处理。

  1. 在一个切片上存储指针或带指针的值。
go 复制代码
func main() {
    s := make([]*string, 0)
    c := "A"
    s = append(s, &c)

    o := Order{
       OrderID:   123,
       OrderInfo: &OrderInfo{},
    }
    orders := make([]Order, 0)
    orders = append(orders, o)
}
  1. 变量被 interface{} 类型引用 :如果将一个变量赋值给 interface{} 类型的变量,该变量可能会发生内存逃逸。因为 interface{} 类型可以存储任意类型的值,编译器无法确定其具体大小和生命周期,为了安全起见,会将其分配到堆上。
  2. 在 interface 类型上调用方法:在 interface 类型上调用方法都是动态调度的 ------ 方法的真正实现只能在运行时知道。想像一个 io.Reader 类型的变量 r , 调用 r.Read(b) 会使得 r 的值和切片b 的背后存储都逃逸掉,所以会在堆上分配。

如何避免内存逃逸

除了注意上面几种编码方式,尽量避免发生内存逃逸。

runtime/stubs.go:133有个函数叫noescapenoescape可以在逃逸分析中隐藏一个指针 。让这个指针在逃逸分析中不会被检测为逃逸。不过要注意,这个函数只是欺骗了逃逸分析器,实际上指针的引用关系依然存在。

go 复制代码
func test() *int {
    num := 10
    return &num
}

func testNotEscape() *int {
    num := 10
    // 使用 noescape 隐藏指针的引用关系
    ptr := (*int)(noescape(unsafe.Pointer(&num)))
    return ptr
}

func noescape(numPtr unsafe.Pointer) unsafe.Pointer {
    x := uintptr(numPtr)
    return unsafe.Pointer(x ^ 0)
}

func main() {
    test()
    testNotEscape()
}
相关推荐
芝士爱知识a6 小时前
2026年AI面试软件推荐
人工智能·面试·职场和发展·大模型·ai教育·考公·智蛙面试
石去皿7 小时前
大模型面试通关指南:28道高频考题深度解析与实战要点
人工智能·python·面试·职场和发展
C雨后彩虹7 小时前
CAS与其他并发方案的对比及面试常见问题
java·面试·cas·同步·异步·
美团程序员7 小时前
80道经典常见测试面试题
软件测试·面试·职场和发展·软件测试面试
测试秃头怪7 小时前
面试大厂就靠这份软件测试八股文了【含答案】
自动化测试·软件测试·python·功能测试·面试·职场和发展·单元测试
测试杂货铺7 小时前
软件测试面试题大全,你要的都在这。。
自动化测试·软件测试·python·功能测试·面试·职场和发展·测试用例
职豚求职小程序7 小时前
校园招聘——荣耀2025秋招,有哪些值得注意的信息?(含荣耀笔面试攻略)
面试·职场和发展
java1234_小锋8 小时前
Java高频面试题:SpringBoot为什么要禁止循环依赖?
java·开发语言·面试
索荣荣9 小时前
Java Session 全面指南:原理、应用与实践(含 Spring Boot 实战)
java·spring boot·后端
千寻技术帮10 小时前
10333_基于SpringBoot的家电进存销系统
java·spring boot·后端·源码·项目·家电进存销