Go 语言作为一门新语言,在早期经常遭到唾弃的就是在垃圾回收(下称:GC)机制中 STW(Stop-The-World)的时间过长。

那么这个时候,我们又会好奇一点,作为 STW 的起始,Go 语言中什么时候才会触发 GC 呢?

今天就由煎鱼带大家一起来学习研讨一轮。

什么是 GC

在计算机科学中,垃圾回收(GC)是一种自动管理内存的机制,垃圾回收器会去尝试回收程序不再使用的对象及其占用的内存。

最早 John McCarthy 在 1959 年左右发明了垃圾回收,以简化 Lisp 中的手动内存管理的机制(来自 @wikipedia)。

图来自网络

为什么要 GC

手动管理内存挺麻烦,管错或者管漏内存也很糟糕,将会直接导致程序不稳定(持续泄露)甚至直接崩溃。

GC 触发场景

GC 触发的场景主要分为两大类,分别是:

  1. 系统触发:运行时自行根据内置的条件,检查、发现到,则进行 GC 处理,维护整个应用程序的可用性。

  2. 手动触发:开发者在业务代码中自行调用 runtime.GC 方法来触发 GC 行为。

系统触发

在系统触发的场景中,Go 源码的 src/runtime/mgc.go 文件,明确标识了 GC 系统触发的三种场景,分别如下:

const (gcTriggerHeap gcTriggerKind = iotagcTriggerTimegcTriggerCycle
)
  • gcTriggerHeap:当所分配的堆大小达到阈值(由控制器计算的触发堆的大小)时,将会触发。

  • gcTriggerTime:当距离上一个 GC 周期的时间超过一定时间时,将会触发。-时间周期以 runtime.forcegcperiod 变量为准,默认 2 分钟。

  • gcTriggerCycle:如果没有开启 GC,则启动 GC。

    • 在手动触发的 runtime.GC 方法中涉及。

手动触发

在手动触发的场景下,Go 语言中仅有 runtime.GC 方法可以触发,也就没什么额外的分类的。

但我们要思考的是,一般我们在什么业务场景中,要涉及到手动干涉 GC,强制触发他呢?

需要手动强制触发的场景极其少见,可能会是在某些业务方法执行完后,因其占用了过多的内存,需要人为释放。又或是 debug 程序所需。

基本流程

在了解到 Go 语言会触发 GC 的场景后,我们进一步看看触发 GC 的流程代码是怎么样的,我们可以借助手动触发的 runtime.GC 方法来作为突破口。

核心代码如下:

func GC() {n := atomic.Load(&work.cycles)gcWaitOnMark(n)gcStart(gcTrigger{kind: gcTriggerCycle, n: n + 1})gcWaitOnMark(n + 1)for atomic.Load(&work.cycles) == n+1 && sweepone() != ^uintptr(0) {sweep.nbgsweep++Gosched()}for atomic.Load(&work.cycles) == n+1 && atomic.Load(&mheap_.sweepers) != 0 {Gosched()}mp := acquirem()cycle := atomic.Load(&work.cycles)if cycle == n+1 || (gcphase == _GCmark && cycle == n+2) {mProf_PostSweep()}releasem(mp)
}
  1. 在开始新的一轮 GC 周期前,需要调用 gcWaitOnMark 方法上一轮 GC 的标记结束(含扫描终止、标记、或标记终止等)。

  2. 开始新的一轮 GC 周期,调用 gcStart 方法触发 GC 行为,开始扫描标记阶段。

  3. 需要调用 gcWaitOnMark 方法等待,直到当前 GC 周期的扫描、标记、标记终止完成。

  4. 需要调用 sweepone 方法,扫描未扫除的堆跨度,并持续扫除,保证清理完成。在等待扫除完毕前的阻塞时间,会调用 Gosched 让出。

  5. 在本轮 GC 已经基本完成后,会调用 mProf_PostSweep 方法。以此记录最后一次标记终止时的堆配置文件快照。

  6. 结束,释放 M。

在哪触发

看完 GC 的基本流程后,我们有了一个基本的了解。但可能又有小伙伴有疑惑了?

本文的标题是 “GC 什么时候会触发 GC”,虽然我们前面知道了触发的时机。但是....Go 是哪里实现的触发的机制,似乎在流程中完全没有看到?

监控线程

实质上在 Go 运行时(runtime)初始化时,会启动一个 goroutine,用于处理 GC 机制的相关事项。

代码如下:

func init() {go forcegchelper()
}func forcegchelper() {forcegc.g = getg()lockInit(&forcegc.lock, lockRankForcegc)for {lock(&forcegc.lock)if forcegc.idle != 0 {throw("forcegc: phase error")}atomic.Store(&forcegc.idle, 1)goparkunlock(&forcegc.lock, waitReasonForceGCIdle, traceEvGoBlock, 1)// this goroutine is explicitly resumed by sysmonif debug.gctrace > 0 {println("GC forced")}gcStart(gcTrigger{kind: gcTriggerTime, now: nanotime()})}
}

在这段程序中,需要特别关注的是在 forcegchelper 方法中,会调用 goparkunlock 方法让该 goroutine 陷入休眠等待状态,以减少不必要的资源开销。

在休眠后,会由 sysmon 这一个系统监控线程来进行监控、唤醒等行为:

func sysmon() {...for {...// check if we need to force a GCif t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 {lock(&forcegc.lock)forcegc.idle = 0var list gListlist.push(forcegc.g)injectglist(&list)unlock(&forcegc.lock)}if debug.schedtrace > 0 && lasttrace+int64(debug.schedtrace)*1000000 <= now {lasttrace = nowschedtrace(debug.scheddetail > 0)}unlock(&sched.sysmonlock)}
}

这段代码核心的行为就是不断地在 for 循环中,对 gcTriggerTimenow 变量进行比较,判断是否达到一定的时间(默认为 2 分钟)。

若达到意味着满足条件,会将 forcegc.g 放到全局队列中接受新的一轮调度,再进行对上面 forcegchelper 的唤醒。

堆内存申请

在了解定时触发的机制后,另外一个场景就是分配的堆空间的时候,那么我们要看的地方就非常明确了。

那就是运行时申请堆内存的 mallocgc 方法。核心代码如下:

func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {shouldhelpgc := false...if size <= maxSmallSize {if noscan && size < maxTinySize {...// Allocate a new maxTinySize block.span = c.alloc[tinySpanClass]v := nextFreeFast(span)if v == 0 {v, span, shouldhelpgc = c.nextFree(tinySpanClass)}...spc := makeSpanClass(sizeclass, noscan)span = c.alloc[spc]v := nextFreeFast(span)if v == 0 {v, span, shouldhelpgc = c.nextFree(spc)}...}} else {shouldhelpgc = truespan = c.allocLarge(size, needzero, noscan)...}if shouldhelpgc {if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {gcStart(t)}}return x
}
  • 小对象:如果申请小对象时,发现当前内存空间不存在空闲跨度时,将会需要调用 nextFree 方法获取新的可用的对象,可能会触发 GC 行为。

  • 大对象:如果申请大于 32k 以上的大对象时,可能会触发 GC 行为。

总结

在这篇文章中,我们介绍了 Go 语言触发 GC 的两大类场景,并分别基于大类中的细分场景进行了一一说明。

一般来讲,我们对其了解大概就可以了。若小伙伴们对其内部具体实现感兴趣,也可以以文章中的代码具体再打开看。

但需要注意,很有可能 Go 版本一升级,可能又变了,学思想要紧!

你对 Go 语言的 GC 有什么想法呢,欢迎在评论区留言交流 :)

关注煎鱼,吸取他的知识 

Go 触发 GC 的时机有哪些?能手动触发吗?相关推荐

  1. golang触发gc的时机

    参考:Golang什么时候会触发GC - 简书 总结一下,一共有主动和被动两种: 主动就是:RD调用runtime.GC来触发,但是一般不会自己调 被动就是分为两种:一种是搞一个定时器,定时来GC:一 ...

  2. Go 都在什么时候触发GC,能手动触发GC吗?

    Go 语言作为一门新语言,在早期经常遭到唾弃的就是在垃圾回收(下称:GC)机制中 STW(Stop-The-World)的时间过长. 那么这个时候,我们又会好奇一点,作为 STW 的起始,Go 语言中 ...

  3. android定时调用gc,Android性能:远程触发GC

    一.远程触发GC原理 我们都知道 GC 是java虚拟机释放内存的机制. 一般的在当前进程触发GC有两种方式: 主动触发.调用System.gc() 被动触发.预分配的内存不足 or OOM之前 有没 ...

  4. 欧盟eCall的两种触发方式——手动触发、自动触发

    参考标准:EN 16072 Intelligent transport systems - ESafety - Pan-European eCall operating requirements 3. ...

  5. JVM内存模型、逃逸分析以及发生GC的时机

    文章目录 1. 整体内存模型图 ①:堆 ②:栈与栈帧 ③:方法区 ④:程序计数器 ⑤:本地方法栈 2. JVM参数设置 GC日志打印参数设置 3. JVM内部的对象创建流程(new 对象流程) 4. ...

  6. java什么时候触发gc_什么时候触发 GC

    什么时候触发 GC 什么时候触发Young GC----针对年轻代 当Eden区满了的时候,会触发Young GC 什么时候触发 Full GC----针对整个堆在发生Young GC的时候,虚拟机会 ...

  7. Android 自动化触发GC

    问题 最近有个小需求,能通过自动化对app进行GC回收 对于app的处理无外乎主动调用System.gc()或者使用adb命令直接进行GC回收 解决方法 方法一 在代码里的某个方法调用System.g ...

  8. [Android]手动触发OnClick事件

    一.实现代码 1.1 将下面代码写在类中,注意不是方法内部!     OnClickListener OnClickEvent = new OnClickListener(){             ...

  9. dropzonejs vue 使用_如何在Dropzone上手动触发上传文件事件

    我将Dropzonejs很好地集成到了我的前端(VueJS)中.如何在Dropzone上手动触发上传文件事件 我有验收测试Dropzone使用Webdriver/Codeception的问题.底线是W ...

最新文章

  1. MySQL登录时ERROR 1045:Access denied for user ‘root’@’localhost’ (using password: YES)
  2. 【内网福音】如何离线部署Rancher
  3. 性价比超高:苹果发布了新数据集,助力室内场景理解
  4. PacBio Sequel概述
  5. 搜索专题:问题 E: 挑战ACM迷宫
  6. 一个女程序员的第七年工作总结
  7. Android系统的开机画面显示过程分析(8)
  8. 剑指 Offer 53 - I. 在排序数组中查找数字 I(二分法)
  9. 如何连接安卓手机_安卓手机如何使用AirPods
  10. html 判断当前窗口是否是子窗口,JavaScript window.open 判断子窗口是否已经存在
  11. 当前时间加30分钟_男性早晨坚持慢跑30分钟,一段时间后,或许这些变化不请自来...
  12. pyecharts绘制地铁图_安利一个绘制地铁线路KMZ的利器 号称国产谷歌地球
  13. 分表分库解决方案(mycat,tidb,shardingjdbc)
  14. matlab建模与仿真应用pdf,MATLAB建模与仿真应用教程 第2版
  15. 统计相关系数(1)——Pearson(皮尔逊)相关系数及MATLAB实现
  16. CEC2018:动态多目标测试函数DF6~DF9的PS及PF(提供Matlab代码)
  17. [转帖]Dockerfile设置默认时区
  18. tdr上升时间什么设定_TDR的完整形式是什么?
  19. 最基本的几种 CSS 文字滤镜效果 - 蓝色理想
  20. MyBatis学习总结-06:动态SQL

热门文章

  1. Agile: 为什么要使用 scrum 而不是瀑布?
  2. Spring Cloud Config 规范 1
  3. 老百姓需要这样的智慧城市
  4. GO学习第三天——自定义包
  5. windows 安装leopard方法
  6. .Net Framework SDK下的命令汇总
  7. mysql中使用sqldriverconnect()报错的解决
  8. 类和接口的使用-类和成员可访问性最小化
  9. 八年磨一剑,阿里云ApsaraDB for HBase2.0正式上线 1
  10. zabbix监控php-fpm性能状态