Go - 内存

内存分配

Go 的内存主要分为两种:栈内存和堆内存

栈内存

  • 协程私有:每个 goroutine 启动时会分配独立的栈空间,默认为 2KB
  • 自动管理:编译器实现内存的自动分配和释放
  • 局部存储:存储局部变量、函数参数、返回值等声明周期明确的数据
  • 极高性能:分配和释放仅需移动栈指针,无锁且无需 GC 参与
  • 动态增长:栈空间不足时,Go 会分配更大的连续内存,将旧数据拷贝到新栈,并更新指针
  • 动态缩容:空间利用率低时,可能会在 GC 阶段回收部分栈内存(但行为较为保守,以避免频繁扩缩容损耗性能)

堆内存

  • 全局共享:堆内存由全部 goroutine 共享,由 Go 运行时统一管理
  • GC 管理:堆内存依赖 GC 自动回收不再使用的对象
  • 灵活性:支持分配任意生命周期的对象
    • 逃逸的变量
    • 大对象(如 []byte 缓冲区大小超过栈容量)
    • 共享数据(被多个 goroutine 访问的对象)

逃逸分析

Go 编译器在编译阶段决定变量分配在栈还是堆,规则如下:

分配在栈的

  • 未逃逸的局部变量

逃逸到堆的

  • 变量的声明周期长于创建它的函数,例如:
    • 返回局部变量的指针
    • 局部变量被闭包引用
    • 通过 channel 发送的指针或者引用类型
  • 无法在逃逸分析期间确定变量类型的。例如使用 interface 类型的变量
  • 变量大小超过栈容量,或者无法静态确定

也需要注意,当函数复杂度较低,行数较少时(具体阈值由 Go 编译器决定),可能会被内联优化。此时可能我们主观认为会逃逸到堆上的变量,不再需要逃逸到堆上而在栈上分配(也有可能变的更容易逃逸)

内连(inline)是一种编译器优化技术,通过将函数调用替换为函数体本身的代码,减少函数调用开销以提升性能

Licensed under CC BY-NC-SA 4.0
最后更新于 Jan 12, 2021 00:00 UTC