Go编译器应用逃逸分析机制:所有默认值放置在栈上,但如果变量“逃离”作用域(例如,返回一个指向局部变量的指针),它会自动放置在堆上。
这对性能很重要:
编译器尝试确定是否可以将对象放置在栈上,但如果它隐含地“存活”超出函数边界 — 则放入堆中。
逃逸示例:
func NewPoint() *int { a := 42 return &a // 逃到堆上! }
没有逃逸的示例:
func Sum(a, b int) int { c := a + b return c // 在栈上 }
要进行编译器诊断,请使用go build -gcflags='-m',以查看详细信息:
go build -gcflags='-m' main.go
“如果仅返回值而不是指针,对象会逃到堆上吗?”
人们常常认为任何返回的值“都会逃到堆上”。实际上,如果返回的是值(而不是指针),变量可能会保持在栈上。
示例:
func F() int { x := 10 return x // 栈分配,未逃到堆 }
故事
在通过工厂函数大量创建结构时,返回指向局部变量的指针,内存消耗和GC负荷急剧增加。结果发现:所有对象都由于返回指针到局部变量而逃到堆上,尽管可以通过返回值来避免。
故事
在微服务中,由于在goroutine之间传递指针并从不同函数返回,许多对象从栈“溢出”到堆中,导致性能下降和GC频繁暂停。
故事
开发者意外地将静态数组包裹在切片中,返回指向切片的指针——在负载测试中开始捕获内存泄漏。诊断显示:变量并未超出函数范围,但由于间接使用,Go错误地决定将其放入堆中。