GoLang内存模型详细讲解

目录
  • 栈内存-协程栈-调用栈
  • 逃逸分析
  • go 堆内存
  • 堆如何进行分配
  • go 语言对象的垃圾回收
  • 如何减少GC对性能的分析
  • GC 优化效率

栈内存-协程栈-调用栈

为什么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内存模型内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Golang 内存模型The Go Memory Model

    目录 1. 简介(Introduction) 2. 建议(Advice) 3. 发生在…之前(Happens Before) 3.1 重排序 3.2 happens-before 3.3 规则 4. 同步(Synchronization) 4.1 初始化(Initialization) 4.2 Go协程的创建(Goroutine creation) 4.3 Go协程的销毁(Goroutine destruction) 4.4 信道通信(Channel communication) 有缓存chan

  • Golang 内存模型详解(一)

    开始之前 首先,这是一篇菜B写的文章,可能会有理解错误的地方,发现错误请斧正,谢谢. 为了治疗我的懒癌早期,我一次就不写得太多了,这个系列想写很久了,每次都是开了个头就没有再写.这次争取把写完,弄成一个系列. 此 nil 不等彼 nil 先声明,这个标题有标题党的嫌疑. Go 的类型系统是比较奇葩的,nil 的含义跟其它语言有些差别,这里举个例子(可以直接进入 http://play.golang.org/p/ezFhXX0dnB 运行查看结果): 复制代码 代码如下: package main

  • GoLang内存模型详细讲解

    目录 栈内存-协程栈-调用栈 逃逸分析 go 堆内存 堆如何进行分配 go 语言对象的垃圾回收 如何减少GC对性能的分析 GC 优化效率 栈内存-协程栈-调用栈 为什么go的栈是在堆上? go 协程栈的位置: go的协程栈位于go的堆内存,go 的gc 也是对堆上内存进行GC, go堆内存位于操作系统虚拟内存上, 记录局部变量,传递参数和返回值 ,go 使用的参数拷贝传递,如果传递的值比较大 注意传递其指针 go 参数传递 使用 值传递, 也就是说传递结构体时候,拷贝结构体的指针,传递结构体指针

  • Golang内存模型教科书级讲解

    目录 1. 简介(Introduction) 2. 建议(Advice) 3. 发生在…之前(Happens Before) 3.1 重排序 3.2 happens-before 3.3 规则 4. 同步(Synchronization) 4.1 初始化(Initialization) 4.2 Go协程的创建(Goroutine creation) 4.3 Go协程的销毁(Goroutine destruction) 4.4 信道通信(Channel communication) 有缓存chan

  • Go语言开发必知的一个内存模型细节

    目录 引言 内存模型定义是什么 happens-before 是什么 A 不一定 happens-before B Go 语言中的 happens-before 定义 Go Channel 实例 例子 1 例子 2 例子 3 例子 4 总结 引言 在日常工作中,如果我们能够了解 Go 语言内存模型,那会带来非常大的作用.这样在看一些极端情况,又或是变态面试题的时候,就能够明白程序运行表现下的很多根本原因了. 当然,靠一篇普通文章讲完 Go 内存模型,不可能.因此今天这篇文章,把重点划在给大家讲解

  • 华为技术专家讲解JVM内存模型(收藏)

    全是干货的技术号: 本文已收录在[github面试知识仓库],欢迎 star/fork: https://github.com/Wasabi1234/Java-Interview-Tutorial 内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的实时运行. JVM内存布局规定了Java在运行过程中内存申请.分配.管理的策略,保证了JVM的高效稳定运行.不同的JVM对于内存的划分方式和管理机制存在着部分差异.结合JVM虚拟机规范,来探讨经典的JVM内存布局. J

  • GoLang并发机制探究goroutine原理详细讲解

    目录 1. 进程与线程 2. goroutine原理 3. 并发与并行 3.1 在1个逻辑处理器上运行Go程序 3.2 goroutine的停止与重新调度 3.3 在多个逻辑处理器上运行Go程序 通常程序会被编写为一个顺序执行并完成一个独立任务的代码.如果没有特别的需求,最好总是这样写代码,因为这种类型的程序通常很容易写,也很容易维护.不过也有一些情况下,并行执行多个任务会有更大的好处.一个例子是,Web 服务需要在各自独立的套接字(socket)上同时接收多个数据请求.每个套接字请求都是独立的

  • GoLang函数栈的使用详细讲解

    目录 函数栈帧 寄存器 函数栈帧 我们的代码会被编译成机器指令并写入到可执行文件,当程序执行时,可执行文件被加载到内存,这些机器指令会被存储到虚拟地址空间中的代码段,在代码段内部,指令是低地址向高地址堆积的.堆区存储的是需要程序员手动alloc并free的空间,需要自己来控制. 虚拟内存空间是对存储器的一层抽象,是为了更好的来管理存储器,虚拟内存和存储器之间存在映射关系. 如果在一个函数中调用了另外一个函数,编译器就会对应生成一条call指令,当call指令被执行时,就会跳转到被调用函数入口处开

  • C语言全部内存操作函数的实现详细讲解

    memcpy内存拷贝函数 void* memcpy(void* destination, const void* source, size_t num); memcpy函数从source的位置开始向后拷贝num个字节的数据到destination的内存位置 这个函数在遇到\0的时候并不会停下来 如果source和destination有任何的重叠,复制的结果都是未定义的 使用方法: #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #in

  • 详细分析Java内存模型

    目录 一.为什么要学习并发编程 二.为什么需要并发编程 三.从物理机中得到启发 四.Java 内存模型 五.原子性 5.1.什么是原子性 5.2.如何保证原子性 六.可见性 6.1.什么是可见性 6.2.如何保证可见性 七.有序性 7.1.什么是有序性 7.2.如何保证有序性 一.为什么要学习并发编程 对于 "我们为什么要学习并发编程?" 这个问题,就好比 "我们为什么要学习政治?" 一样,我们(至少作为学生党是这样)平常很少接触到,然后背了一堆 "正确且

随机推荐