Golang面试系列3-内存管理

3.1 内存分配机制

Go内存管理本质上是一个经过内部优化的内存池:自动伸缩内存池大小,合理的切割内存块

分配逻辑:针对不同大小对象有不同的分配逻辑

  • (0,16B)且不含指针的对象:Tiny分配
  • (0,16B)且含指针的对象:正常分配
  • [16B,32KB]对象:正常分配
  • (32kB,-):大对象分配

内存分配流程图示

流程描述

  1. 获取当前线程的私有缓存mcache【不存在线程竞争,提高内存并发申请效率】;

  2. 根据对象size计算出合适的classId;

  3. 基于classId从mcache的alloc[class]链表中查询可用的span;

  4. 若mcache中不存在对应的span,则从mcentral中申请新的span加入mcache中;

  5. 若mcentrral中不存在可用的span,则从mheap中申请新的span加入到mcentral中;

  6. 从该span中获取到空闲对象地址并返回。

3.2 垃圾回收机制

垃圾回收就是对程序中不再使用的内存资源进行自动回收的操作。

3.2.1 概述
1. 变更历史

GoV1.3- 普通标记清除法:整体过程需要启动 STW,效率极低。

GoV1.5- 三色标记法+写屏障:堆空间启动,栈空间不启动,全部扫描之后,需要重新扫描一次栈 (需要 STW),效率普通。

GoV1.8 - 三色标记法+混合写屏障机制:堆栈空间启动,整个过程几乎不需要 STW,效率较高。

2. GC触发条件
  • 系统触发:当活跃对象达到堆内存的GC阈值(默认25%)时,将会触发 —设置环境变量GOGC调整阈值
  • 系统监控:当超过两分钟没有产生任何GC时,强制触发GC
  • 步调算法:控制内存增长的比例,当内存分配达到一定的比例则触发GC
  • 手动触发:开发者在业务代码中自行调用runtime.GC方法来触发
3. 优化策略

1)避免循环引用:通过代码审查和工具分析来检查潜在的循环引用问题,并确保及时释放不再需要的对象

2)调整GC参数:在程序启动时通过设置环境变量 GOGC 来调整 GC 的触发阈值,降低GC频率

export GOGC=100 

3)在程序开始时设置GC并发度

//设置GC并发度为X
runtime.GOMAXPROCS(X)

4)pprof性能分析:在程序中开启 pprof 性能监控

import _ "net/http/pprof"
go func() {log.Println(http.ListenAndServe("localhost:6060", nil))
}()

5)设置小堆空间(eg:512MB)

go build -ldflags "-H=windowsgui -X 'runtime/debug.MaxMemory=536870912'"
3.2.2 常用垃圾回收算法
1. 引用计数

原理:每个对象维护一个引用计数器,当对象被引用时计数自动+1,当引用失效时,计数自动-1,计数为0时回收该对象。

优点:对象可以很快被收回,不会出现内存耗尽/达到阈值才回收。

缺点:无法很好地处理循环引用。

2. 标记-清除

原理:从根节点开始遍历所有引用的对象,引用的对象被标记为“被引用”,没有被标记的则被回收。

优点:可以较好地处理循环引用。

缺点

        需要 STW(stop the world),暂时停止程序运行;

        效率较低;

        内存碎片化问题严重,无法为较大对象分配内存;

3. 复制算法

原理:按容量将内存等分为两块,每次只使用一块,当该内存满后,将该内存中存活的对象复制到另一块,并将已使用的内存清除。

缺点

        存活对象较多时,效率较低;

        内存空间利用率低,只有原本的1/2;

4. 标记-整理

原理:标记出所有需要回收的对象,将存活的对象移动到内存一端,清除掉边界外的对象。

优点:解决内存碎片化问题

5. 分代收集

原理:按照对象生命周期长短划分不同的代空间,生命周期长的放入老年代,短的放入新生代,不同代使用不同的回收算法和频率。

优点:性能较好

缺点:算法复杂

3.2.3 Go垃圾回收机制

Go使用无分代,不整理并发(与用户代码并发执行)的三色标记清除算法

TIPS:

不分代:Go编译器会通过逃逸分析将大部分新生对象存储在栈上,只有需要长时间存在的对象会被分配到堆中。

不整理:整理是为了解决内存碎片化问题,而Go内存分配器与tcmalloc类似,基本不存在内存碎片。

1. 三色标记清除算法

1)对象分类

  • 灰色(波面):已被回收器访问的对象,但回收器需要对其中的一个或多个指针进行扫描,因为它们可能还指向白色对象。
  • 黑色(确定存活):已被回收器访问的对象,其中所有字段都已被扫描,黑色对象中的任何一个指针都不可能直接指向白色对象。
  • 白色(可能死亡):未被回收器访问到的对象。开始回收阶段对象均为白色,回收结束后白色对象不可达。

2)标记清除过程

  • 第一步:初始状态下,所有对象均为白色对象
  • 第二步:从根节点开始遍历所有对象,把遍历到的对象标记为灰色对象,放入待处理队列;
  • 第三步:从待处理队列取出灰色对象,将其引用的对象标记为灰色对象并放入待处理队列,并将其自身标记为黑色对象
  • 循环第三步,直到所有灰色对象变为黑色。
  • 通过写屏障(write-barrier)检测到对象变化,重复以上操作

TIPS:如果对象的引用被用户修改了,那么之前的标记就无效了。因此Go采用了写屏障技术,当对象新增或者更新会将其着色为灰色

  • 收集所有的白色对象(垃圾/不可达对象)
2. STW性能影响分析

1)STW(stop the world)是什么?

  • 在 GC 的过程中,为了避免对象之间的引用关系发生新的变更导致GC结果发生错误【如GC过程中用户程序为"黑色对象"新增了一个引用,会导致该引用对象被误清理】,故GC过程中会停止所有运行的协程。
  • STW过程中,CPU 不执行用户代码,全部用于垃圾回收。
  • STW对性能有一些影响,Golang目前已经可以做到ms级别的STW。

2)如果取消STW会有什么问题?

  • 问题分析

  • 结论:不暂停程序时,如果用户程序在标记阶段改变了对象引用关系,会影响标记结果的正确性,可能会导致对象丢失。
  • 发生条件:同时满足以下两个条件时,就会出现对象丢失。

条件 1: 一个白色对象被黑色对象引用 (白色被挂在黑色下)

条件 2: 灰色对象与该白色对象的可达关系遭到破坏 (灰色同时丢了该白色)

  • 解决方案:可以利用屏障机制,尝试去破坏上面的两个必要条件。

3. Go GC性能优化原理

原理:可以通过屏障机制在保证对象不丢失的情况下,尽可能地缩短STW的时间,从而提高GC性能。

1)“强-弱”三色不变式

  • 强三色不变式:不存在黑色对象引用到白色对象的指针。
  • 弱三色不变式:所有被黑色对象引用的白色对象都处于灰色保护状态。

​2)插入写屏障(遵循强三色不变式,堆空间启动)

操作:若对象A在堆区,在 A 对象引用 B 对象的时候,B 对象被标记为灰色。

缺点:结束时需要 STW 来重新扫描栈,标记栈上引用的白色对象的存活。

处理流程

  • 第一步:所有对象标记为白色
  • 第二步:遍历一次Root Set,将可达对象记录灰色

  • 第三步:遍历灰色对象列表,标记其可达对象为灰色,并将遍历后的灰色标记为黑色

  • 第四步:重点来了!!!! 由于并发特性,此刻用户协程向黑色对象1,4添加了白色对象9,8

    tips:对象4在堆区,会立即触发插入屏障机制,而对象1在栈区,不会触发。

  • 第五步:继续循环上述流程进行三色标记,直到不存在灰色对象

  • 第六步:在垃圾回收之前,启动STW,重新扫描栈区对象进行三色标记,标记完成后,停止STW(约10~100ms)

  • 第七步:清除堆&栈内存空间的白色对象,GC完成。

3)删除写屏障(遵循弱三色不变式,堆栈空间启动)

操作:被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。

缺点:回收精度低,GC 开始时 STW 扫描堆栈来记录初始快照,这个过程会保护开始时刻的所有存活对象。

核心处理流程

4)混合写屏障

实现原理:结合了插入和删除写屏障的优点。开始时并发扫描各个 Goroutine 的栈区,使其对象变黑并一直保持,这个过程不需要 STW;标记结束后,因为栈在扫描后始终是黑色的,也无需再进行 re-scan 操作了,减少了 STW 的时间

处理流程

  • 第一步:GC 开始将栈上的对象全部扫描并标记为黑色 (之后不再进行第二次重复扫描,无需 STW)
  • 第二步:GC 期间,任何在栈上创建的新对象,均为黑色。
  • 第三步:被删除/添加的对象均标记为灰色。

3.3 逃逸分析(Escape analysis)

3.3.1 概述

编译器决定对象分配的内存位置(堆/栈),逃逸分析在编译阶段完成。

函数申请一个新对象:

  • 若分配在栈内存中,则函数执行结束后可自动回收内存;
  • 若分配在堆内存中,则函数执行结束后交由GC处理;
3.3.2 逃逸策略

当函数申请新对象时,编译器会根据该对象是否被外部引用来决定是否逃逸

  • 若不存在外部引用,优先放在栈内存中【注:若对象所需内存超过栈的存储能力,则会放在堆中】
  • 若存在外部引用,必定放在堆内存
3.3.3 逃逸场景

主要有以下场景:指针逃逸,栈空间不足逃逸,动态类型逃逸,闭包对象引用逃逸

tips:通过以下命令行查看逃逸分析

## 查看编译过程中的逃逸分析
go build -gcflags=-m xxx.go## 分析结果:出现内存逃逸
"escapes to heap"
  • 指针逃逸:函数内返回局部变量指针
type Student struct {Name stringAge  int
}// PointerEscape 指针逃逸
func PointerEscape(name string, age int) *Student {s := new(Student) //局部变量逃逸到堆内存中s.Name = names.Age = agereturn s
}
  • 栈空间不足逃逸:当栈空间不足以存放当前对象或者无法判断当前切片长度时会将对象分配到堆中
func Slice() {s := make([]int, 1000, 1000) //切片长度过大for index, _ := range s {s[index] = index}
}
  • 动态类型逃逸:很多函数参数为interface类型,编译期间很难确定其参数的具体类型,也会产生逃逸
func DynamicTypeEscape() {str := "Escape"fmt.Println(str)
}
  • 闭包引用对象逃逸:闭包中的局部变量会因为闭包的引用产生逃逸
func Fibonacci() func() int {a, b := 0, 1return func() int {a, b = b, a+b //会产生逃逸return a}
}

TIPS:函数传递指针真的比传值效率高吗?

传指针可以通过减少值拷贝过程来提高效率,但是指针传递可能会内存逃逸,增加GC负担,可能会降低效率。

参考文档: 

Golang三色标记混合写屏障GC模式全分析 | Go 技术论坛

内存分配原理-地鼠文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/295851.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

推荐多样性 - 华为OD统一考试(C卷)

OD统一考试(C卷) 分值: 200分 题解: Java / Python / C++ 题目描述 推荐多样性需要从多个列表中选择元素,一次性要返回N屏数据(窗口数量),每屏展示K个元素(窗口大小),选择策略: 各个列表元素需要做穿插处理,即先从第一个列表中为每屏选择一个元素,再从第二个列表…

k8s 基础入门

1.namespace k8s中的namespace和docker中namespace是两码事,可以理解为k8s中的namespace是为了多租户,dockers中的namespace是为了网络、资源等隔离 2.deployment kubectl create #新建 kubectl aply #新建 更新 升级: 滚动升级&#x…

pygame--坦克大战(一)

项目搭建 本游戏主要分为两个对象,分别是我方坦克和敌方坦克。用户可以通过控制我方的坦克来摧毁敌方的坦克保护自己的“家”,把所有的敌方坦克消灭完达到胜利。敌方的坦克在初始的时候是默认5个的(这可以自己设置),当然,如果我方坦克被敌方坦克的子弹打中,游戏结束。从…

蓝色wordpress外贸建站模板

蓝色wordpress外贸建站模板 https://www.mymoban.com/wordpress/7.html

Ansys Zemax | 如何将光栅数据从Lumerical导入至OpticStudio(上)

附件下载 联系工作人员获取附件 本文介绍了一种使用Ansys Zemax OpticStudio和Lumerical RCWA在整个光学系统中精确仿真1D/2D光栅的静态工作流程。将首先简要介绍方法。然后解释有关如何建立系统的详细信息。 本篇内容将分为上下两部分,上部将首先简要介绍方法工…

C++——异常机制

目录 一,背景 1.1 C语言处理错误的方式 1.2 C异常概念 二,异常的使用 2.1 异常的简单使用 2.2 异常的匹配原则 2.3 异常抛对象 2.4 异常的重新抛出 2.5 异常安全 三,自定义异常体系 四,异常优缺点 4.1 优点 4.2 缺点 …

声音文件格式有哪几种?常见的声音格式和转换方法分享

随着数字技术的飞速发展,声音文件已经成为了我们日常生活和工作中不可或缺的一部分。无论是音乐、电影、游戏还是各类应用程序,声音文件都扮演着重要的角色。本文将为大家介绍常见的声音文件格式以及如何进行格式转换。 一、常见的声音文件格式 &#x…

shopee虾皮业绩一直没办法提升?不同时期要有不同的运营思路

店铺运营“开荒期”需要根据自身店铺数据调整运营策略,“运营期”就需要更多分析竞品的运营数据,分析接近上架时间段的出单同款/相似款,有效找到影响起量的因素;在出单缓慢,接近瓶颈期时找同行的策略方案,抓…

filetype: python中判断图像格式库imghdr替代库

引言 imghdr库是python中的一个内置库,用来判断图像原本格式的。自己一直有在用,不过近来看到这个库在python 3.13中会被移除。 自己感觉一直被python版本赶着走。这不找了好久,才找到一个替代库–filetype Python各个版本将要移除和可替代…

后台返回数据需要自己匹配图标,图标命名与后台返回的变量保持一致

testItemId为后台返回匹配图标的变量名 sportsTargetsData:{suggestSportTargetId: "2",unlocks: [{ testItemId: vo2max_high_knee, sportTargetName: 心肺能力, indexName: 心肺能力, sportTargetId: 1 },{ testItemId: grip_strength, sportTargetName: 基础力量…

第12章 集合框架

一 集合框架概述 1.1 生活中的容器 1.2 数组的特点与弊端 一方面,面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用数组存储对象方面具有一些弊端,而Java 集合…

Linux学习笔记————C 语言版 LED 灯实验

这里写目录标题 一、实验程序编写二、 汇编部分实验程序编写三、C 语言部分实验程序编写四、编译下载验证 汇编 LED 灯实验中,我们讲解了如何使用汇编来编写 LED 灯驱动,实际工作中是很少用到汇编去写嵌入式驱动的,毕竟汇编太难,而…

Java中的可变字符串

Java中的可变字符串 一、什么是可变字符串二、可变字符串的使用场景以及使用步骤1.新建一个可变字符串2.可变字符串的一系列方法 一、什么是可变字符串 可变字符串是Java.lang包下的 在我们学习到JDBC的时候需要将原有的sql语句根据不同的差异添加一段新的关键字或者单词&…

Spring Boot 学习(2)——HelloWorld

HelloWorld!全宇宙码农的第一个(行)程序(代码)。 1、创建项目 打开idea,新建一个maven项目。 1)选择项目sdk(本例是1.8) 2)输入GroupId(co…

DDL ---- 数据库的操作

1.查询所有数据库 show databases; 上图除了自创的,其他的四个都是mysql自带的数据库 。(不区分大小写) 2.查询当前数据库 select database(); 最开始没有使用数据库,那么查找结果为NULL 所以我们就需要先使用数据库&#xff…

Spring Boot 配置文件

1. 配置文件的作用 配置文件主要是为了解决硬件编码带来的问题,把可能会发生改变的信息,放在一个集中的地方,当我们启动某个程序时,程序从配置文件中读取一些数据,并加载运行。 硬编码是将数据直接放在源代码中&…

【HTML】标签学习(下.2)

(大家好哇,今天我们将继续来学习HTML(下.2)的相关知识,大家可以在评论区进行互动答疑哦~加油!💕) 目录 二.列表标签 2.1 无序列表(重点) 2.2有序列表(理解) 2.3 自定义列表(重点…

数字信号处理实验操作教程:3-3 mp3音频编码实验(AD7606采集)

一、实验目的 学习AD7606采集音频数据的方法并掌握MP3音频编码的原理,并实现AD7606采集音频数据进行MP3编码并保存到SD卡。 二、实验原理 AD7606原理图 硬件原理图,找到AD采集,可查看相关控制引脚,同时可看到ADC输入的V1V8通道…

稀碎从零算法笔记Day37-LeetCode:所有可能的真二叉树

今天的每日一题,感觉理解的还不够深,有待加深理解 题型:树、分治、递归 链接:894. 所有可能的真二叉树 - 力扣(LeetCode) 来源:LeetCode 题目描述 给你一个整数 n ,请你找出所有…

记Postman参数化

因为需要在WEB页面上处理部分数据,手动操作太慢,所以考虑使用接口方式处理,因急于使用,用Python Request的方式,写代码也来得慢,故采用Postman加外部文件参数化方式来实现。 接口请求是Post方式&#xff0c…