一,Go 内存分析之-逃逸分析
Go 程序会在 2 个地方为变量分配内存,一个是全局的堆(heap)空间用来动态分配内存,另一个是每个 goroutine 的栈(stack)空间,这里就引出了堆(heap)与栈(stack)
内存分配到栈空间上,随着goroutine的生命周期结束自动释放,而分配在全局堆中就会借助GC了。
Go采用标记清除的方式,同时引入三色标记算法\STW和写屏障技术(Write Barrier)
标记清除收集器是跟踪式垃圾收集器,其执行过程可以分成标记(Mark)和清除(Sweep)两个阶段:
- 标记阶段 — 从根对象出发查找并标记堆中所有存活的对象;
- 清除阶段 — 遍历堆中的全部对象,回收未被标记的垃圾对象并将回收的内存加入空闲链表。
标记清除算法的一个典型耗时是在标记期间,需要暂停程序(Stop the world,STW),标记结束之后,用户程序才可以继续执行。
所以,当有变量不能随着程序结束而结束时,就发生了内存逃逸
出现的几种场景:
1,引用了指针
package main
import "fmt"
type Demo struct {
name string
}
func createDemo(name string) *Demo {
d := new(Demo) // 局部变量 d 逃逸到堆
d.name = name
return d
}
func main() {
demo := createDemo("demo")
fmt.Println(demo)
}
2 interface{}动态类型
3 闭包
4 栈空间不足,goroutine初始内存大小为2K,而一个栈空间为8M
引出另一个问题
如果利用内存逃逸分析做性能优化
传值 VS 传指针
传值会拷贝整个对象,而传指针只会拷贝指针地址,指向的对象是同一个。传指针可以减少值的拷贝,但是会导致内存分配逃逸到堆中,增加垃圾回收(GC)的负担。在对象频繁创建和删除的场景下,传递指针导致的 GC 开销可能会严重影响性能。
1,对于需要修改原对象值
2,占用内存比较大的结构体,选择传指针。对于只读的占用内存较小的结构体,直接传值能够获得更好的性能。