【GC】垃圾回收原理分析

news2024/9/24 11:32:55

本文基于小许先生的编程世界学习了gc机制。
面试官:了解gc机制吗?简述一下。
我:golang为清除不再被使用的对象,回收内存的机制。采用了三色标记法+混合写屏障。首先开启屏障,一旦在gc期间发生指针引用变化,删除或添加就标记为灰色,gc开始是将所有对像置为白色,扫描根对象将他标记为灰色入队,扫描他指向的对象入队置为灰色,将本身置黑;重复扫描队列中的对象直到对象为空,然后将没有被引用的对象清除。
面试官:gc会有stw吗?
我:在1.3的时候,golang在gc时stw标记清除,期间程序停止,有卡顿
在1.5的时候,golang在gc采用了三色标记法,渐进扫描,减少了stw时间,清除期间stw防止gc的时候对象引用改变,被错误清除
在1.8的时候加入混合写屏障,在对象引用变化后会通知垃圾回收器重新扫描,没有全局stw,只是扫描具体的栈的时候需要停止相应的goroutinue
面试官:什么时候发生gc?
我:1.系统触发,堆内存分配达到阈值,距离上一次分配已经达到超时时间
2.手动触发
不处于gc、允许gc、程序没有崩溃

0.前言

垃圾回收(Garbage Collection,简称 GC)是一种内存管理策略,由垃圾收集器以类似守护协程的方式在后台运作,按照既定的策略为用户回收那些不再被使用的对象,释放对应的内存空间.
(1)GC 带来的优势包括:

  • 屏蔽内存回收的细节
    拥有 GC 能力的语言能够为用户屏蔽复杂的内存管理工作,使用户更好地聚焦于核心的业务逻辑.
  • 以全局视野执行任务
    现代软件工程项目体量与日剧增,一个项目通常由团体协作完成,研发人员负责各自模块的同时,不可避免会涉及到临界资源的使用. 此时由于缺乏全局的视野,手动对内存进行管理无疑会增加开发者的心智负担. 因此,将这部分工作委托给拥有全局视野的垃圾回收模块来完成,方为上上之策.
    (2)GC 带来的劣势:
  • 提高了下限但降低了上限
    将释放内存的工作委托给垃圾回收模块,研发人员得到了减负,但同时也失去了控制主权. 除了运用有限的GC调优参数外,更多的自由度都被阉割,需要向系统看齐,服从设定.
  • 增加了额外的成本
    全局的垃圾回收模块化零为整,会需要额外的状态信息用以存储全局的内存使用情况. 且部分时间需要中断整个程序用以支持垃圾回收工作的执行,这些都是GC额外产生的成本.
    (3)GC 的总体评价
    除开少量追求极致速度的特殊小规模项目之外,在绝大多数高并发项目中,GC模块都为我们带来了极大的裨益,已经成为一项不可或缺的能力.

1.垃圾回收的方法

1.1标记清除

在这里插入图片描述

缺点:
这是一种类似于排除法的间接处理思路,不直接查找垃圾对象,而是标记存活对象,从而取补集推断出垃圾对象.
至于标记清扫算法的不足之处,通过上图也得以窥见一二,那就是会产生内存碎片. 经过几轮标记清扫之后,空闲的内存块可能零星碎片化分布,此时倘若有大对象需要分配内存,可能会因为内存空间无法化零为整从而导致分配失败.
1.2标记压缩
在这里插入图片描述

标记压缩(Mark-Compact)算法,是在标记清扫算法的基础上做了升级,在第二步”清扫“的同时还会对存活对象进行压缩整合,使得整体空间更为紧凑,从而解决内存碎片问题.
标记压缩算法在功能性上呈现得很出色,而其存在的缺陷也很简单,就是实现时会有很高的复杂度.

1.3半空间复制

[图片]

相信用过 Java 的同学对半空间复制(Semispace Copy)算法并不会感到陌生,它的核心点如下:
1.分配两片相等大小的空间,称为 fromspace 和 tospace
2. 每轮只使用 fromspace 空间,以GC作为分水岭划分轮次
3. GC时,将fromspace存活对象转移到tospace中,并以此为契机对空间进行压缩整合
4. GC后,交换fromspace和tospace,开启新的轮次
显然,半空间复制算法应用了以空间换取时间的优化策略,解决了内存碎片的问题,也在一定程度上降低了压缩空间的复杂度. 但其缺点也同样很明显——比较浪费空间.

1.4引用计数

[图片]

1.对象每被引用一次,计数器加1
2. 对象每被删除引用一次,计数器减1
3. GC时,把计数器等于 0 的对象删除
然而,这个朴素的算法存在一个致命的缺陷:无法解决循环引用或者自引用问题.

2.各个版本go语言垃圾回收的发展

1.3以前:stw
golang的垃圾回收算法都非常简陋,其性能也广被诟病:go runtime在一定条件下(内存超过阈值或定期如2min),暂停所有任务的执行,进行mark&sweep操作,操作完成后启动所有任务的执行。在内存使用较多的场景下,go程序在进行垃圾回收时会发生非常明显的卡顿现象(Stop The World)。在对响应速度要求较高的后台服务进程中,这种延迟简直是不能忍受的!这个时期国内外很多在生产环境实践go语言的团队都或多或少踩过gc的坑。当时解决这个问题比较常用的方法是尽快控制自动分配内存的内存数量以减少gc负荷,同时采用手动管理内存的方法处理需要大量及高频分配内存的场景。
1.3:Mark STW & Sweep
1.3版本中,go runtime分离了mark和sweep操作,和以前一样,也是先暂停所有任务执行并启动mark,mark完成后马上就重新启动被暂停的任务了,而是让sweep任务和普通协程任务一样并行的和其他任务一起执行。如果运行在多核处理器上,go会试图将gc任务放到单独的核心上运行而尽量不影响业务代码的执行。go team自己的说法是减少了50%-70%的暂停时间。
1.5:三色标记
go 1.5正在实现的垃圾回收器“非分代的、非移动的、并发的、三色的标记清除垃圾收集器”。这种方法的mark操作是可以渐进执行的而不需每次都扫描整个内存空间,可以减少stop the world的时间。 由此可以看到,一路走来直到1.5版本,go的垃圾回收性能也是一直在提升。
1.8:混合写屏障(hybrid write barrier)
由于标记操作和用户逻辑是并发执行的,用户逻辑会时常生成对象或者改变对象的引用。例如把⼀个对象标记为白色准备回收时,用户逻辑突然引用了它,或者又创建了新的对象。由于对象初始时都看为白色,会被 GC 回收掉,为了解决这个问题,引入了写屏障机制。
GC 对扫描过后的对象使⽤操作系统写屏障功能来监控这段内存。如果这段内存发⽣引⽤改变,写屏障会给垃圾回收器发送⼀个信号,垃圾回收器捕获到信号后就知道这个对象发⽣改变,然后重新扫描这个对象,看看它的引⽤或者被引⽤是否改变。利⽤状态的重置实现当对象状态发⽣改变的时候,依然可以再次其引用的对象。

3.GO的gc

3.1三色标记

  • 白色对象:潜在的垃圾,其内存可能会被垃圾收集器回收;
  • 黑色对象:活跃的对象,包括不存在任何引用外部指针的对象以及从根对象可达的对象;
  • 灰色对象:活跃的对象,因为存在指向白色对象的外部指针,垃圾收集器会扫描这些对象的子对象;
    三色标记垃圾收集器的工作原理很简单,我们可以将其归纳成以下几个步骤:
    [图片]

1.从灰色对象的集合中选择一个灰色对象并将其标记成黑色;
2.将黑色对象指向的所有对象都标记成灰色,保证该对象和被该对象引用的对象都不会被回收;
3.重复上述两个步骤直到对象图中不存在灰色对象。
当三色的标记清除的标记阶段结束之后,应用程序的堆中就不存在任何的灰色对象,我们只能看到黑色的存活对象以及白色的垃圾对象,垃圾收集器可以回收这些白色的垃圾
[图片]

因为用户程序可能在标记执行的过程中修改对象的指针,所以三色标记清除算法本身是不可以并发或者增量执行的,它仍然需要 STW,否则:用户在gc时候引用D,或许D会被错误回收
[图片]

本来不应该被回收的对象却被回收了,这在内存管理中是非常严重的错误,我们将这种错误称为悬挂指针,即指针没有指向特定类型的合法对象,影响了内存的安全性,想要并发或者增量地标记对象还是需要使用屏障技术。

3.2混合写屏障

想要在并发或者增量的标记算法中保证正确性,我们需要达成以下两种三色不变性(Tri-color invariant)中的一种:

  • 强三色不变性:黑色对象不会指向白色对象,只会指向灰色对象或者黑色对象;(写屏障)
  • 弱三色不变性:黑色对象指向的白色对象必须包含一条从灰色对象经由多个白色对象的可达路径。(删除写屏障)

垃圾收集中的屏障技术更像是一个钩子方法,它是在用户程序读取对象、创建新对象以及更新对象指针时执行的一段代码,根据操作类型的不同,我们可以将它们分成读屏障(Read barrier)和写屏障(Write barrier)两种,因为读屏障需要在读操作中加入代码片段,对用户程序的性能影响很大,所以编程语言往往都会采用写屏障保证三色不变性。
Go 语言在 v1.8 组合 Dijkstra 插入写屏障和 Yuasa 删除写屏障构成混合写屏障,该写屏障会将被覆盖的对象标记成灰色并在当前栈没有扫描时将新对象也标记成灰色。
为了移除栈的重扫描过程,除了引入混合写屏障之外,在垃圾收集的标记阶段,我们还需要将创建的所有新对象都标记成黑色,防止新分配的栈内存和堆内存中的对象被错误地回收,因为栈内存在标记阶段最终都会变为黑色,所以不再需要重新扫描栈空间。

3.3.增量和并发

传统的垃圾收集算法会在垃圾收集的执行期间暂停应用程序,一旦触发垃圾收集,垃圾收集器会抢占 CPU 的使用权占据大量的计算资源以完成标记和清除工作,然而很多追求实时的应用程序无法接受长时间的 STW。
为了减少应用程序暂停的最长时间和垃圾收集的总暂停时间,我们会使用下面的策略优化现代的垃圾收集器:

  • 增量垃圾收集:增量地标记和清除垃圾,降低应用程序暂停的最长时间;
  • 并发垃圾收集:利用多核的计算资源,在用户程序执行时并发标记和清除垃圾;
    因为增量和并发两种方式都可以与用户程序交替运行,所以我们需要使用屏障技术保证垃圾收集的正确性;与此同时,应用程序也不能等到内存溢出时触发垃圾收集,因为当内存不足时,应用程序已经无法分配内存,这与直接暂停程序没有什么区别,增量和并发的垃圾收集需要提前触发并在内存不足前完成整个循环,避免程序的长时间暂停
    增量收集
    增量式(Incremental)的垃圾收集是减少程序最长暂停时间的一种方案,它可以将原本时间较长的暂停时间切分成多个更小的 GC 时间片,虽然从垃圾收集开始到结束的时间更长了,但是这也减少了应用程序暂停的最大时间:
    [图片]

需要注意的是,增量式的垃圾收集需要与三色标记法一起使用,为了保证垃圾收集的正确性,我们需要在垃圾收集开始前打开写屏障,这样用户程序修改内存都会先经过写屏障的处理,**保证了堆内存中对象关系的强三色不变性或者弱三色不变性。**虽然增量式的垃圾收集能够减少最大的程序暂停时间,但是增量式收集也会增加一次 GC 循环的总时间,在垃圾收集期间,因为写屏障的影响用户程序也需要承担额外的计算开销,所以增量式的垃圾收集也不是只带来好处的,但是总体来说还是利大于弊。
并发收集
并发(Concurrent)的垃圾收集不仅能够减少程序的最长暂停时间,还能减少整个垃圾收集阶段的时间,通过开启读写屏障、利用多核优势与用户程序并行执行,并发垃圾收集器确实能够减少垃圾收集对应用程序的影响:
[图片]

虽然并发收集器能够与用户程序一起运行,但是并不是所有阶段都可以与用户程序一起运行,部分阶段还是需要暂停用户程序的,不过与传统的算法相比,并发的垃圾收集可以将能够并发执行的工作尽量并发执行;当然,因为读写屏障的引入,并发的垃圾收集器也一定会带来额外开销,不仅会增加垃圾收集的总时间,还会影响用户程序,这是我们在设计垃圾收集策略时必须要注意的。

4.GC的时机

运行时会通过如下所示的 runtime.gcTrigger.test 方法决定是否需要触发垃圾收集,当满足触发垃圾收集的基本条件时 — 允许垃圾收集、程序没有崩溃并且没有处于垃圾收集循环,该方法会根据三种不同方式触发进行不同的检查:

func (t gcTrigger) test() bool {
// 允许垃圾收集、程序没有崩溃并且没有处于垃圾收集循环
        if !memstats.enablegc || panicking != 0 || gcphase != _GCoff {
                return false
        }
        switch t.kind {
        case gcTriggerHeap:
        //堆内存的分配达到达控制器计算的触发堆大小;
                return memstats.heap_live >= memstats.gc_trigger
        case gcTriggerTime:
        //如果一定时间内没有触发,就会触发新的循环,
        //该触发条件由 runtime.forcegcperiod 变量控制,默认为 2 分钟;
                if gcpercent < 0 {
                        return false
                }
                lastgc := int64(atomic.Load64(&memstats.last_gc_nanotime))
                return lastgc != 0 && t.now-lastgc > forcegcperiod
        case gcTriggerCycle:
        //如果当前没有开启垃圾收集,则触发新的循环;
                return int32(t.n-work.cycles) > 0
        }
        return true
}

用于开启垃圾收集的方法 runtime.gcStart 会接收一个 runtime.gcTrigger 类型的结构,所有出现 runtime.gcTrigger 结构体的位置都是触发垃圾收集的代码:
a.runtime.sysmon 和 runtime.forcegchelper :后台运行定时检查和垃圾收集;
b.runtime.GC :用户程序手动触发垃圾收集;
c.runtime.mallocgc :申请内存时根据堆大小触发垃圾收集。
如果你对gc的源码有兴趣,指路小徐先生的编程世界

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决启动Nginx时80端口被占用的问题

文章目录 前言 解决方法1. 搜索服务打开![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c7600d266d194bdb864f7660a5bc5890.png)2. 找到World Wide Web 发布服务这个服务&#xff0c;右键属性&#xff0c;将它的启动类型改为手动&#xff0c;然后停止掉该服务。3. 此…

虚拟化技术:新能源汽车空调控制系统的智能新突破

汽车生产中&#xff0c;空调系统已经成为标配&#xff0c;空调系统的性能是衡量一辆汽车是否舒适的重要指标之一。 01.汽车空调系统组成 (1) 制冷系统&#xff1a;制冷系统的功能是给汽车内部提供冷空气&#xff0c;主要由压缩机、冷凝器、膨胀阀以及蒸发器组成。首先由压缩机…

释放国产光耦合器的潜力

光耦合器&#xff0c;也称为光隔离器&#xff0c;已成为现代电子产品中不可或缺的组件。它们的主要功能是在电路的不同部分之间提供电气隔离&#xff0c;确保安全并防止干扰。随着技术的发展&#xff0c;光耦合器也在不断发展&#xff0c;国产光耦合器现在在性能和成本方面都具…

回归预测|基于粒子群优化核极限学习机PSO-KELM结合Adaboost集成的数据预测Matlab程序 多特征输入单输出

回归预测|基于粒子群优化核极限学习机PSO-KELM结合Adaboost集成的数据预测Matlab程序 多特征输入单输出 文章目录 一、基本原理1. 数据预处理2. PSO优化&#xff08;粒子群优化&#xff09;3. KELM训练&#xff08;核极限学习机&#xff09;4. AdaBoost集成5. 模型评估和优化6.…

004: VTK读入数据---vtkImageData详细说明

VTK医学图像处理---vtkImageData类 目录 VTK医学图像处理---vtkImageData类 简介&#xff1a; 1 Mricro软件的安装和使用 (1) Mricro安装 (2) Mricro转换DICOM为裸数据 2 从硬盘读取数据到vtkImageData 3 vtkImageData转RGB或RGBA格式 4 练习 总结 简介&#xff1a;…

Minikube Install Kubernetes v1.18.1

文章目录 简介安装工具配置代理运行集群检查集群加入rancher 简介 模拟客户环境&#xff0c;测试 kubernetes v1.18.x 是否可以被 rancher v2.9.1 纳管。 安装工具 docker 安装Install and Set Up kubectl on Linux 安装 minikube 配置代理 docker proxylinux proxy 运行…

python进阶篇-day07-进程与线程

day06进程与线程 一. 进程 每个软件都可以看作是一个进程(数据隔离) 软件内的多个任务可以看作是多个线程(数据共享) 单核CPU: 宏观并行, 微观并发 真正的并行必须有多核CPU 多任务介绍 概述 多任务指的是, 多个任务"同时"执行 目的 节约资源, 充分利用CPU资源, …

万龙觉醒辅助:VMOS云手机辅助前期宝物选择!万龙觉醒怎么挂机?

《万龙觉醒》作为一款策略战争类游戏&#xff0c;玩家们在日常进行游戏时&#xff0c;可能会遇到游戏时间不足或无法长时间在线的问题。而通过使用VMOS云手机&#xff0c;可以实现24小时游戏云端在线托管&#xff0c;无需手动操作&#xff0c;彻底解放双手&#xff0c;随时随地…

【人工智能学习笔记】1_人工智能基础

本系列是个人学习《阿里云人工智能工程师ACA认证免费课程&#xff08;2023版&#xff09;》的笔记&#xff0c;仅为个人学习记录&#xff0c;欢迎交流&#xff0c;感谢批评指正 人工智能概述 智能的三大能力&#xff1a;感知、记忆与思维、学习与适应能力人工智能的定义 明斯基…

4 个最佳 3D 数据可视化工具(免费和付费)

摘要: 在当今的技术时代,数据可视化在软件测试领域发挥着相当重要的作用。简而言之,数据可视化是设计并分析数据视觉表示的过程。其中有一些顶级的数据可视化工具超出了工作范围。在数据可视化工具的帮助下,变得相当简单、更加准确。这是因为这些工具进一步消除了对手工劳动…

经济不景气?相反,这才是普通人赚钱的绝佳机会!

“日子越来越难了!”身边类似的抱怨越来越多。 想想也是&#xff0c;这两年市场低迷、房地产暴雷、各行业内卷.....即便兜里有钱的&#xff0c;也面临资产缩水的风险。 但好在从去年开始&#xff0c;国内外AI企业黑马连出&#xff0c;AI文本、图片、视频生产模型直接颠覆了传…

1990-2022年各地级市gdp、一二三产业gdp及人均gdp数据

1990-2022年各地级市gdp、一二三产业gdp及人均gdp数据 1、时间&#xff1a;1990-2022年 2、来源&#xff1a;城市统计年鉴 3、指标&#xff1a;年度、城市名称、城市代码、城市类别、省份标识、省份名称、国内生产总值/亿元、第一产业占GDP比重(%)、第二产业占GDP比重(%)、第…

Redis - 主从复制

文章目录 目录 文章目录 前言 1. 配置 建立复制 断开复制 传输延时 2. 主从拓扑结构 一主一从 一主多从 树状 三. 原理 数据同步 psync replicationid/replid(复制id) master_replid 和 master_replid2 offset (偏移量) psync 运行流程 全量复制 部分复制 …

算法进阶 | 必知!5大深度生成模型!

本文来源公众号“算法进阶”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;必知&#xff01;5大深度生成模型&#xff01; 随着Sora、diffusion、GPT等模型的大火&#xff0c;深度生成模型又成为了大家的焦点。 深度生成模型是一…

【简历】25届南京某一本JAVA简历:简历通过率还好,但是拿不到OFFER

注&#xff1a;为保证用户信息安全&#xff0c;姓名和学校等信息已经进行同层次变更&#xff0c;内容部分细节也进行了部分隐藏 简历说明 今天看一份25届南京某一本大学的Java简历。 这个简历呢&#xff0c;学校是一本。我们说上来先要定校招层次&#xff0c;这个层次就按照…

D35XT120-ASEMI新能源专用D35XT120

编辑&#xff1a;ll D35XT120-ASEMI新能源专用D35XT120 型号&#xff1a;D35XT120 品牌&#xff1a;ASEMI 封装&#xff1a;DXT-5 批号&#xff1a;2024 现货&#xff1a;50000 正向电流&#xff08;Id&#xff09;&#xff1a;35A 反向耐压&#xff08;VRRM&#xff0…

dubbo是什么?,能做什么?以及其工作流程

1.Dubbo是什么&#xff1f;能做什么&#xff1f; Dubbo是阿里巴巴开源的基于Java的高性能RPC分布式服务框架&#xff0c;现已成为Apache基金会孵化项目&#xff0c;致力于提供高性能和透明化的RPC远程服务调用方案&#xff0c;以及SOA服务治理方案 简单来说&#xff0c;dubbo…

通义灵码怎么样?分为哪些版本,看看基础能力多少分?

通义灵码&#xff0c;是一款基于通义大模型的智能编码辅助工具&#xff0c;提供行级/函数级实时续写、自然语言生成代码、单元测试生成、代码优化、注释生成、代码解释、研发智能问答、异常报错排查等能力&#xff0c;并针对阿里云的云服务使用场景调优&#xff0c;助力开发者高…

【计算机组成原理】计算机系统层次结构

计算机系统层次结构 计算机系统是一个层次结构系统&#xff0c;每一层都通过向上层用户提供一个抽象的简洁接口而将低层的实现细节隐藏起来。计算机解决应用问题的过程就是不同抽象层进行转换的过程 计算机系统抽象层的转换 下图描述了从最终用户希望计算机完成的应用(问题)…

Java重修笔记 第四十九天 Collections 工具类

Collections 工具类 1. public static void reverse(List<?> list) 反转集合中元素的顺序 2. public static void shuffle(List<?> list) 将集合里的元素顺序打乱 3. public static <T extends Comparable<? super T>> void sort(List<T>…