GoLang内存模型详细讲解

栈内存-协程栈-调用栈 为什么go的栈是在堆上? go 协程栈的位置: go的协程栈位于go的堆内存,go 的gc 也是对堆上内存进行GC, go堆内存位于操作

栈内存-协程栈-调用栈

为什么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的栈初始空间为2KB
  • 在函数调用前判断栈空间morestack
  • 早期使用分段栈(go 1.13) 在逻辑上连接,优点没有空间浪费,栈指针会在不连续的空间跳转,后期 连续栈,缺点 伸缩时候开销大,扩容为原来2倍, 使用比例不足1/4, 变为原来的 1/2

go 堆内存

操作系统的虚拟内存:操作系统给应用提供的虚拟的内存的空间,背后也是物理内存或者磁盘

go 使用 heapArena每次申请虚拟内存单元 64MB,所有的heapArena 组成 堆内存

线性分配据或者链表分配出现空间碎片,所有go 语言中使用分级分配,避免内存的碎片化,每个内存进行分级思想, mspan n内存管理单元

按照需求进行分级分配,runtime.sizeclass.go 进行分配, 总共有68 个级别。

其中 136 个span , mcentral 属于链接头,其中 68个需要GC扫描,其他68个不需要GC扫描。 mcentral 的属于中心索引,使用互斥锁保护,在高并发的场景下 锁冲突严重,参考GMP模型,增加线程的本地缓存。

  • Go 模仿TCmalloc ,建立自己的堆内存架构
  • 使用heapArena 向操作系统申请内存,以mspan 为单位,防止碎片化
  • mcentral 是mspan 的中心索引
  • 使用mcache 本地缓存 大大降低 锁竞争问题

堆如何进行分配

  • Tiny 微对象(0,16B)无指针 – 分配到普通mspan(class 1 - class 67) --将多个微级对象合并成16Byte
  • Small 对象[16B, 32K] – 定制作mspan (class 0)
  • Large 大对象(32KB, +)
  • heapArena 不足的化 会自动申请扩容

go 语言对象的垃圾回收

  • 标记-清除
  • 标记-整理 (go 语言使用分级分配 不需要标记整理)
  • 标记-复制 (只有用内存进行复制,但是空间浪费非常大,Java的新生代) 总结: Go 堆内存的独特方式 进行标记清除掉,如何寻找有用?
  • Gc的起点: 1. 被栈上的指针引用 2.被全局变量引用 3.被寄存器中指针引用
  • Root 节点进行广度优先策略进行搜索(可达性分析标记方法)
  • 暂停所有其他协程,进行可达性分析,找到无引用的GC 属于串行GC 属于在OLD version 中

如何减少GC对性能的分析

如何进行并行GC 提升性能?

难点在于如何进行标记阶段,go 语言采用的 三色标记方法

  • 黑色: 表示已经分析扫描,有用
  • 灰色: 有用,还没进行分析扫描 DFS代替队列
  • 白色: 暂时无用

当三色标记结束后只有黑色的对象,下一次开启恢复成 白色

并发标记的问题(删除)-- 在GC时候 进行对象的指针的变动,针对 并发标记问题 使用 Yuasa 删除屏障, 强制将释放的C指针变成灰色,避免 在GC过程中被粗我䣌标记

Yuasa 删除屏障(s释放的指针进行强制为灰色)

1. 删除屏障可以杜绝在GC标记中删除的问题 ,但是也无法解决并发标记的插入问题

针对插入屏障 使用 Dijkstra 插入屏障 并发标记过程中 将C进行强制置灰,当并发标记过程,新指针指向新的对象,新增的依赖对象 防止错误的GC

混合屏障

被删除的堆对象标记成为灰色

被添加的堆对象标记成为灰色

并发垃圾回收关键在于标记安全,兼顾的安全的效率

GC 优化效率

GC触发的时机

系统定时触发

g0 协程内的sysmon 定时检查 ,在2min 内 forcegcperiod 没有过GC,触发,谨慎调整

用户显示触发

调用runtime.gc 并不推荐

申请内存触发

给申请对象的时候伴随着GC

GC优化原则

尽量少在堆上产生垃圾

内存池化(channel 中 环形池)

减少逃逸 (fmt 包, 返回了指针不是拷贝)

使用空结构体 (不占用空结构体,使用channel 传递空结构体)

使用如下的方式 查看内存

$env:GODEBUG="gctrace=1"

到此这篇关于GoLang内存模型详细讲解的文章就介绍到这了,更多相关Go内存模型内容请搜索好代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持好代码网!

您可能有感兴趣的文章
golang并发编程的如何实现

golang如何实现redis的延时消息队列功能示例

golang中range在slice和map遍历中的注意事项

Golang命令行进行debug调试操作

golang的空标识符理解