Go 触发 GC 的时机有哪些?能手动触发吗?
Go 语言作为一门新语言,在早期经常遭到唾弃的就是在垃圾回收(下称:GC)机制中 STW(Stop-The-World)的时间过长。
那么这个时候,我们又会好奇一点,作为 STW 的起始,Go 语言中什么时候才会触发 GC 呢?
今天就由煎鱼带大家一起来学习研讨一轮。
什么是 GC
在计算机科学中,垃圾回收(GC)是一种自动管理内存的机制,垃圾回收器会去尝试回收程序不再使用的对象及其占用的内存。
最早 John McCarthy 在 1959 年左右发明了垃圾回收,以简化 Lisp 中的手动内存管理的机制(来自 @wikipedia)。
为什么要 GC
手动管理内存挺麻烦,管错或者管漏内存也很糟糕,将会直接导致程序不稳定(持续泄露)甚至直接崩溃。
GC 触发场景
GC 触发的场景主要分为两大类,分别是:
系统触发:运行时自行根据内置的条件,检查、发现到,则进行 GC 处理,维护整个应用程序的可用性。
手动触发:开发者在业务代码中自行调用
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)
}
在开始新的一轮 GC 周期前,需要调用
gcWaitOnMark
方法上一轮 GC 的标记结束(含扫描终止、标记、或标记终止等)。开始新的一轮 GC 周期,调用
gcStart
方法触发 GC 行为,开始扫描标记阶段。需要调用
gcWaitOnMark
方法等待,直到当前 GC 周期的扫描、标记、标记终止完成。需要调用
sweepone
方法,扫描未扫除的堆跨度,并持续扫除,保证清理完成。在等待扫除完毕前的阻塞时间,会调用Gosched
让出。在本轮 GC 已经基本完成后,会调用
mProf_PostSweep
方法。以此记录最后一次标记终止时的堆配置文件快照。结束,释放 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 循环中,对 gcTriggerTime
和 now
变量进行比较,判断是否达到一定的时间(默认为 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 的时机有哪些?能手动触发吗?相关推荐
- golang触发gc的时机
参考:Golang什么时候会触发GC - 简书 总结一下,一共有主动和被动两种: 主动就是:RD调用runtime.GC来触发,但是一般不会自己调 被动就是分为两种:一种是搞一个定时器,定时来GC:一 ...
- Go 都在什么时候触发GC,能手动触发GC吗?
Go 语言作为一门新语言,在早期经常遭到唾弃的就是在垃圾回收(下称:GC)机制中 STW(Stop-The-World)的时间过长. 那么这个时候,我们又会好奇一点,作为 STW 的起始,Go 语言中 ...
- android定时调用gc,Android性能:远程触发GC
一.远程触发GC原理 我们都知道 GC 是java虚拟机释放内存的机制. 一般的在当前进程触发GC有两种方式: 主动触发.调用System.gc() 被动触发.预分配的内存不足 or OOM之前 有没 ...
- 欧盟eCall的两种触发方式——手动触发、自动触发
参考标准:EN 16072 Intelligent transport systems - ESafety - Pan-European eCall operating requirements 3. ...
- JVM内存模型、逃逸分析以及发生GC的时机
文章目录 1. 整体内存模型图 ①:堆 ②:栈与栈帧 ③:方法区 ④:程序计数器 ⑤:本地方法栈 2. JVM参数设置 GC日志打印参数设置 3. JVM内部的对象创建流程(new 对象流程) 4. ...
- java什么时候触发gc_什么时候触发 GC
什么时候触发 GC 什么时候触发Young GC----针对年轻代 当Eden区满了的时候,会触发Young GC 什么时候触发 Full GC----针对整个堆在发生Young GC的时候,虚拟机会 ...
- Android 自动化触发GC
问题 最近有个小需求,能通过自动化对app进行GC回收 对于app的处理无外乎主动调用System.gc()或者使用adb命令直接进行GC回收 解决方法 方法一 在代码里的某个方法调用System.g ...
- [Android]手动触发OnClick事件
一.实现代码 1.1 将下面代码写在类中,注意不是方法内部! OnClickListener OnClickEvent = new OnClickListener(){ ...
- dropzonejs vue 使用_如何在Dropzone上手动触发上传文件事件
我将Dropzonejs很好地集成到了我的前端(VueJS)中.如何在Dropzone上手动触发上传文件事件 我有验收测试Dropzone使用Webdriver/Codeception的问题.底线是W ...
最新文章
- MySQL登录时ERROR 1045:Access denied for user ‘root’@’localhost’ (using password: YES)
- 【内网福音】如何离线部署Rancher
- 性价比超高:苹果发布了新数据集,助力室内场景理解
- PacBio Sequel概述
- 搜索专题:问题 E: 挑战ACM迷宫
- 一个女程序员的第七年工作总结
- Android系统的开机画面显示过程分析(8)
- 剑指 Offer 53 - I. 在排序数组中查找数字 I(二分法)
- 如何连接安卓手机_安卓手机如何使用AirPods
- html 判断当前窗口是否是子窗口,JavaScript window.open 判断子窗口是否已经存在
- 当前时间加30分钟_男性早晨坚持慢跑30分钟,一段时间后,或许这些变化不请自来...
- pyecharts绘制地铁图_安利一个绘制地铁线路KMZ的利器 号称国产谷歌地球
- 分表分库解决方案(mycat,tidb,shardingjdbc)
- matlab建模与仿真应用pdf,MATLAB建模与仿真应用教程 第2版
- 统计相关系数(1)——Pearson(皮尔逊)相关系数及MATLAB实现
- CEC2018:动态多目标测试函数DF6~DF9的PS及PF(提供Matlab代码)
- [转帖]Dockerfile设置默认时区
- tdr上升时间什么设定_TDR的完整形式是什么?
- 最基本的几种 CSS 文字滤镜效果 - 蓝色理想
- MyBatis学习总结-06:动态SQL