浅谈go语言内存逃逸现象

作者:

文章目录
  • Go 的编译阶段发生在您执行 go build、go install 或 go run 命令时,编译器执行的一个静态分析步骤,它通过分析代码的作用域和生命周期,来判断一个局部变量(通常在栈上分配)是否“逃逸”出了它原本的声明范围(比如函数返回后还需要存在)。如果逃逸了,那么这个变量就必须在堆上分配,以便在函数返回后还能被访问。
  • 逃逸分析发生在你之前问题中提到的 编译流程的早期阶段,具体是在: 类型检查之后(因为需要类型信息)。 生成中间代码之前。 所以,它是在纯粹的编译时做出的决定,而不是在运行时。
  • 编译器遵循一个基本原则:只要编译器能证明函数返回后,该变量不再被引用,那么它就会被分配在栈上。否则,就必须分配在堆上。 以下是导致变量逃逸到堆上的常见情况: 返回局部变量的指针这是最典型的例子。如果函数返回了一个局部变量的地址,那么这个局部变量必须在堆上分配,因为它在函数返回后依然有效。 func foo() *int { v := 100 // 局部变量 v return &v // 返回 v 的地址。v 会逃逸到堆上。 } func main() { p := foo() fmt.Println(*p) } 将指针存储到全局变量或包级变量中局部变量的生命周期超过了函数本身,因此必须分配在堆上。 var global *int func bar() { x := 1 // x 会逃逸到堆上,因为 global 在 bar 函数返回后仍然存在。 global = &x } 发送指针或带有指针的值到 ChannelGo 的 Channel 是引用类型,发送操作可能导致变量被其他 goroutine 访问,其生命周期无法在编译时确定,因此会逃逸。 type Data struct { S string } func main() { ch := make(chan *Data, 1) d := Data{S: “hello”} // d 会逃逸到堆上 ch <- &d } 在切片或 Map 中存储指针如果切片或 Map 本身逃逸了(比如被返回了),那么其中存储的指针所指向的变量也会逃逸。 func getMap() map[string]*int { m := make(map[string]*int) val := 10 // val 会逃逸到堆上 m[“key”] = &val return m } 由于动态大小或接口调用当切片的大小在编译时无法确定(比如根据参数动态分配),或者值被存储到 interface{} 中(因为接口的方法调用是动态的),编译器可能会采取保守策略,让其逃逸。 func createSlice(n int) { s := make([]int, n) // 如果 n 非常大或者在编译时未知,s 可能会逃逸 // … 使用 s }
  • go build -gcflags=”-m -l” main.go
  • 从专业角度看,内存逃逸是 Go 语言内存管理系统的核心机制之一: 安全性优先:逃逸分析确保指针始终指向有效内存,避免悬垂指针 编译时决策:在编译阶段确定变量分配位置,而非运行时 性能权衡:在内存安全性和执行效率之间取得平衡 透明性:对开发者透明,但了解机制有助于写出更高效的代码 到此这篇关于浅谈go语言内存逃逸现象的文章就介绍到这了,更多相关go语言内存逃逸内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 您可能感兴趣的文章: golang内存逃逸分析 golang内存逃逸的学习笔记 golang防止内存逃逸的方法小结 一文搞懂Golang中的内存逃逸 详解Golang的GC和内存逃逸 Golang内存管理之内存逃逸分析 Golang学习之内存逃逸分析 浅析Golang中的内存逃逸 一文搞懂Golang中的内存逃逸 浅谈Golang内存逃逸
  • 目录
    • 什么是逃逸分析?
    • 逃逸分析发生在哪个阶段?
    • 编译器如何决定:栈 vs. 堆?
    • 逃逸分析命令
    • 总结

    Go 的编译阶段发生在您执行 go build、go install 或 go run 命令时,编译器执行的一个静态分析步骤,它通过分析代码的作用域和生命周期,来判断一个局部变量(通常在栈上分配)是否“逃逸”出了它原本的声明范围(比如函数返回后还需要存在)。如果逃逸了,那么这个变量就必须在堆上分配,以便在函数返回后还能被访问。

    逃逸分析发生在你之前问题中提到的 编译流程的早期阶段,具体是在:

    类型检查之后(因为需要类型信息)。

    生成中间代码之前。

    所以,它是在纯粹的编译时做出的决定,而不是在运行时。

    编译器遵循一个基本原则:只要编译器能证明函数返回后,该变量不再被引用,那么它就会被分配在栈上。否则,就必须分配在堆上。

    以下是导致变量逃逸到堆上的常见情况:

    1. 返回局部变量的指针
      这是最典型的例子。如果函数返回了一个局部变量的地址,那么这个局部变量必须在堆上分配,因为它在函数返回后依然有效。
    func foo() *int {
        v := 100 // 局部变量 v
        return &v // 返回 v 的地址。v 会逃逸到堆上。
    }
    
    func main() {
        p := foo()
        fmt.Println(*p)
    }
    
    1. 将指针存储到全局变量或包级变量中
      局部变量的生命周期超过了函数本身,因此必须分配在堆上。
    var global *int
    
    func bar() {
        x := 1 // x 会逃逸到堆上,因为 global 在 bar 函数返回后仍然存在。
        global = &x
    }
    
    1. 发送指针或带有指针的值到 Channel
      Go 的 Channel 是引用类型,发送操作可能导致变量被其他 goroutine 访问,其生命周期无法在编译时确定,因此会逃逸。
    type Data struct {
        S string
    }
    
    func main() {
        ch := make(chan *Data, 1)
        d := Data{S: "hello"} // d 会逃逸到堆上
        ch <- &d
    }
    
    1. 在切片或 Map 中存储指针
      如果切片或 Map 本身逃逸了(比如被返回了),那么其中存储的指针所指向的变量也会逃逸。
    func getMap() map[string]*int {
        m := make(map[string]*int)
        val := 10 // val 会逃逸到堆上
        m["key"] = &val
        return m
    }
    
    1. 由于动态大小或接口调用
      当切片的大小在编译时无法确定(比如根据参数动态分配),或者值被存储到 interface{} 中(因为接口的方法调用是动态的),编译器可能会采取保守策略,让其逃逸。
    func createSlice(n int) {
        s := make([]int, n) // 如果 n 非常大或者在编译时未知,s 可能会逃逸
        // ... 使用 s
    }
    

    go build -gcflags="-m -l" main.go
    

    从专业角度看,内存逃逸是 Go 语言内存管理系统的核心机制之一:

    安全性优先:逃逸分析确保指针始终指向有效内存,避免悬垂指针

    编译时决策:在编译阶段确定变量分配位置,而非运行时

    性能权衡:在内存安全性和执行效率之间取得平衡

    透明性:对开发者透明,但了解机制有助于写出更高效的代码

    到此这篇关于浅谈go语言内存逃逸现象的文章就介绍到这了,更多相关go语言内存逃逸内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

    您可能感兴趣的文章:

    • golang内存逃逸分析
    • golang内存逃逸的学习笔记
    • golang防止内存逃逸的方法小结
    • 一文搞懂Golang中的内存逃逸
    • 详解Golang的GC和内存逃逸
    • Golang内存管理之内存逃逸分析
    • Golang学习之内存逃逸分析
    • 浅析Golang中的内存逃逸
    • 一文搞懂Golang中的内存逃逸
    • 浅谈Golang内存逃逸

    站内搜索