为什么go的栈是在堆上?
go 协程栈的位置: go的协程栈位于go的堆内存,go 的gc 也是对堆上内存进行GC, go堆内存位于操作系统虚拟内存上, 记录局部变量,传递参数和返回值 ,go 使用的参数拷贝传递,如果传递的值比较大 注意传递其指针
go 参数传递 使用 值传递, 也就是说传递结构体时候,拷贝结构体的指针,传递结构体指针时候 拷贝的结构体指针 所以对于 只读参数,不进行修改,最好传递结构体指针
协程栈的空间不够大 怎么办?
本地变量太大,栈帧太多
不是所有的变量都放在协程栈上,栈帧回收后,需要继续使用的变量,或者 太大的变量,分为指针逃逸,空接口逃逸和大变量逃逸,从栈逃逸分配到堆空间上
指针逃逸 (函数返回的指针被其他使用)
func a() *int { v :=0 return &v // 导致 局部变量会分配在堆行 不会分配栈上 }
空接口逃逸(函数的参数是interface{} 函数的实参很可能会逃逸,主要因为interface{} 类型函数往往会使用反射)
fmt.Println(i) // 入参属于interface{} 空接口, 是否有反射查看值是什么类型 逃逸到堆上
大变量逃逸(过大变量导致的空间不足,超过64KB的变量会逃逸)
// 解决协程的栈空间不足
调用栈帧太多(栈扩容)
操作系统的虚拟内存:操作系统给应用提供的虚拟的内存的空间,背后也是物理内存或者磁盘
go 使用 heapArena每次申请虚拟内存单元 64MB,所有的heapArena 组成 堆内存
线性分配据或者链表分配出现空间碎片,所有go 语言中使用分级分配,避免内存的碎片化,每个内存进行分级思想, mspan n内存管理单元
按照需求进行分级分配,runtime.sizeclass.go 进行分配, 总共有68 个级别。
其中 136 个span , mcentral 属于链接头,其中 68个需要GC扫描,其他68个不需要GC扫描。 mcentral 的属于中心索引,使用互斥锁保护,在高并发的场景下 锁冲突严重,参考GMP模型,增加线程的本地缓存。
如何进行并行GC 提升性能?
难点在于如何进行标记阶段,go 语言采用的 三色标记方法
当三色标记结束后只有黑色的对象,下一次开启恢复成 白色
并发标记的问题(删除)-- 在GC时候 进行对象的指针的变动,针对 并发标记问题 使用 Yuasa 删除屏障, 强制将释放的C指针变成灰色,避免 在GC过程中被粗我䣌标记
Yuasa 删除屏障(s释放的指针进行强制为灰色)
1. 删除屏障可以杜绝在GC标记中删除的问题 ,但是也无法解决并发标记的插入问题
针对插入屏障 使用 Dijkstra 插入屏障 并发标记过程中 将C进行强制置灰,当并发标记过程,新指针指向新的对象,新增的依赖对象 防止错误的GC
混合屏障
被删除的堆对象标记成为灰色
被添加的堆对象标记成为灰色
并发垃圾回收关键在于标记安全,兼顾的安全的效率
GC触发的时机
系统定时触发
g0 协程内的sysmon 定时检查 ,在2min 内 forcegcperiod 没有过GC,触发,谨慎调整
用户显示触发
调用runtime.gc 并不推荐
申请内存触发
给申请对象的时候伴随着GC
GC优化原则
尽量少在堆上产生垃圾
内存池化(channel 中 环形池)
减少逃逸 (fmt 包, 返回了指针不是拷贝)
使用空结构体 (不占用空结构体,使用channel 传递空结构体)
使用如下的方式 查看内存
$env:GODEBUG="gctrace=1"