作者:rocketwang
如果你也是个 Go 开发者,你是否关心过内存的分配和回收呢?创建的对象究竟需要由 GC 进行回收,还是随着调用栈被弹出,就消失了呢? GC 导致的 Stop The World 是否导致了你程序的性能抖动呢?
本文简单介绍了 Go 的内存分配逻辑,介绍了 Go 内存划分模型。并以代码为例子,简要介绍了几种场景下 Go 内存分配逻辑。随后使用go build 命令验证了内存逃逸等相关验证。最后,总结出了内存分配原则。
需要关心内存分配问题
每个工程师的时间都如此宝贵,在继续读这篇文章之前,需要你先回答几个问题,如果得到的答案是否定的,那可能本文章里写的内容对你并没有什么帮助。但是,如果你遇到了因内存分配而导致的性能问题,可能这篇文章能带你理解 Golang 的内存分配的冰山一角,带你入个门。
问题如下:
- 你的程序是性能敏感型吗?
- GC 带来的延迟影响到了你的程序性能吗?
- 你的程序有过多的堆内存分配吗?
如果你命中上面问题的其中一个或两个,那这篇文章适合你继续读下去。或你根本不知道如何回答这些问题,可能去了解下 go 性能观测相关的知识(pprof 的使用等)对你更有帮助。
下面正文开始。
Golang 简要内存划分
可以简单的认为 Golang 程序在启动时,会向操作系统申请一定区域的内存,分为栈(Stack)和堆(Heap)。栈内存会随着函数的调用分配和回收;堆内存由程序申请分配,由垃圾回收器(Garbage Collector)负责回收。性能上,栈内存的使用和回收更迅速一些;尽管Golang 的 GC 很高效,但也不可避免的会带来一些性能损耗。因此,Go 优先使用栈内存进行内存分配。在不得不将对象分配到堆上时,才将特定的对象放到堆中。
内存分配过程分析
本部分,将以代码的形式,分别介绍栈内存分配、指针作为参数情况下的栈内存分配、指针作为返回值情况下的栈内存分配并逐步引出逃逸分析和几个内存逃逸的基本原则。
正文开始,Talk is cheap,show me the code。
栈内存分配
我将以一段简单的代码作为示例,分析这段代码的内存分配过程。
package main
import "fmt"
func main() { n := 4 n2 := square(n) fmt.Println(n2)}
func square(n int) int{ return n * n}