Golang剖析栈内存,协程栈,堆内存结构和分配,垃圾回收

Golang剖析栈内存,协程栈,堆内存结构和分配,垃圾回收,第1张

栈内存

每个协程第一个栈帧为 goexit()
每次调用其他函数会加入一个栈帧

协程栈的作用,协程的执行路径
局部变量(方法内部声明 的变量)

Go的协程栈位于堆内存上(Go特殊的设计),栈的释放也是通过GC来释放的
C++,C他的栈区和堆区是分开的,堆上需要我们进行管理,栈上自行管理
Go的堆内存位于 *** 作系统的虚拟内存上

用户的main方法首先会开辟一块main.main的栈帧

栈帧首先记录栈基址(就是指从哪个方法调用进来的)方便返回的时候知道返回地址在哪
开辟调用方法的返回值,return就是将返回值写回上一个栈帧预留的空间

返回之后将调用的方法栈帧回收
从这看出来Go采用的是参数拷贝传递(值传递)
传递函数体时会拷贝结构体中所有内容,建议传递结构体指针

栈帧太多,(调用方法太多),C++stackoverflow栈溢出

协程栈

协程栈(自己当时看 g 的结构里面的那个 stack)相比线程很小(很轻量4K)
栈帧太多,局部变量太大(一个结构体中变量很多)可能出现的问题:

局部变量太大
逃逸分析(从栈上逃到堆上去)

指针逃逸,空接口逃逸,大变量逃逸
指针逃逸
函数返回了对象的指针
因为函数外面要访问这个地址,他不是一个局部变量,不能作为局部变量处理

空接口逃逸
函数的参数为空接口fmt.Println(i)
函数的实参 i 很可能会逃逸
因为 interface{} 类型的函数往往会使用反射判断一下类型,反射的对象要求在堆上

栈帧太多解决方法栈扩容

分段栈
没有空间浪费
但栈指针会在不连续的空间跳转(开辟用完回收,又开辟又回收)

连续栈
空间不足,扩容2倍后拷贝
使用率不足1/4,空间减半

堆内存结构

进程通过虚拟内存对应着物理内存
如果使用的虚拟内存太大(超过物理内存),OOM ,杀掉

go是一批一批获取虚拟内存的 heapArena
Go每次申请的虚拟内存单元64M,所有的heapArena组成了Go的堆内存mheap
相邻的heapArena可能对应不相邻的虚拟内存单元
相邻的虚拟内存单元可能对应不相邻的物理单元

go采用分级分配(可以容纳的最小空间)
内存管理单元 mspan (span 跨度)
每个mspan 有N个相同 单元格子

堆内存分配

对象分级
Tiny 0 - 16B 无指针 (微对象)
Small 16-32KB
Large 32-
微小对象分配到1-67span
大对象量身定做,分配到 0 span
1级span在17版本使用不到的
拿到2级span(16字节)
将多个微对象拼接合并成一个16字节放入

某种数量的mspan缺少时,会从heapArena开辟新的mspan(grow函数)
heapArena不够会从 *** 作系统虚拟内存申请新的heapArena

垃圾回收

标记-清除(别的语言可能会碎片化,但go不会,因为使用span分级)
标记-整理(整理开销,java老年代频率低可以用整理,使用span分级不需要整理)
标记-复制(减少了整理的开销,但是浪费空间,Java新生代,但是新空间是10份,所以浪费的空间比较小)

go选择最简单的标记清除
从哪开始找:被栈,全局变量,寄存器上的指针引用,上述变量被称为Root Set(GC root)
通过GC root 看可以到达哪些对象

串行GC
STW -> 回收 -> 恢复协程运行 -> …
STW对性能影响,要把串行改成并行

并发的难点在于标记阶段
三色标记法
黑色 有用,已经分析扫描
灰色 有用,还未分析扫描
白色 暂时无用(到最后要清除)
灰色为空时,清除白色,然后黑色变为白色,供下次扫描
三色标记为了之后的并发

并发标记问题:
原本结构体中的指针变为nil
本来的nil指向了一个对象

针对堆对象
插入屏障(增加的引用为灰)
删除屏障(删除的引用为灰)
go采用的是混合屏障(插入+删除)
这样就变成了并发,提高效率

GC优化原则
内存池化(channel 环形)
减少逃逸

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/2991827.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-23
下一篇 2022-09-23

发表评论

登录后才能评论

评论列表(0条)

保存