内存分配
Go 的内存主要分为两种:栈内存和堆内存
栈内存
- 协程私有:每个 goroutine 启动时会分配独立的栈空间,默认为 2KB
- 自动管理:编译器实现内存的自动分配和释放
- 局部存储:存储局部变量、函数参数、返回值等声明周期明确的数据
- 极高性能:分配和释放仅需移动栈指针,无锁且无需 GC 参与
- 动态增长:栈空间不足时,Go 会分配更大的连续内存,将旧数据拷贝到新栈,并更新指针
- 动态缩容:空间利用率低时,可能会在 GC 阶段回收部分栈内存(但行为较为保守,以避免频繁扩缩容损耗性能)
堆内存
- 全局共享:堆内存由全部 goroutine 共享,由 Go 运行时统一管理
- GC 管理:堆内存依赖 GC 自动回收不再使用的对象
- 灵活性:支持分配任意生命周期的对象
- 逃逸的变量
- 大对象(如 []byte 缓冲区大小超过栈容量)
- 共享数据(被多个 goroutine 访问的对象)
逃逸分析
Go 编译器在编译阶段决定变量分配在栈还是堆,规则如下:
分配在栈的
- 未逃逸的局部变量
逃逸到堆的
- 变量的声明周期长于创建它的函数,例如:
- 返回局部变量的指针
- 局部变量被闭包引用
- 通过 channel 发送的指针或者引用类型
- 无法在逃逸分析期间确定变量类型的。例如使用 interface 类型的变量
- 变量大小超过栈容量,或者无法静态确定
也需要注意,当函数复杂度较低,行数较少时(具体阈值由 Go 编译器决定),可能会被内联优化。此时可能我们主观认为会逃逸到堆上的变量,不再需要逃逸到堆上而在栈上分配(也有可能变的更容易逃逸)
内连(inline)是一种编译器优化技术,通过将函数调用替换为函数体本身的代码,减少函数调用开销以提升性能