談?wù)揋o 什么時(shí)候會(huì)觸發(fā) GC問題
在早期經(jīng)常遭到唾棄的就是在垃圾回收(下稱:GC)機(jī)制中 STW(Stop-The-World)的時(shí)間過長。那么這個(gè)時(shí)候,我們又會(huì)好奇一點(diǎn),作為 STW 的起始,Go 語言中什么時(shí)候才會(huì)觸發(fā) GC 呢?
1、什么是 GC
在計(jì)算機(jī)科學(xué)中,垃圾回收(GC)是一種自動(dòng)管理內(nèi)存的機(jī)制,垃圾回收器會(huì)去嘗試回收程序不再使用的對(duì)象及其占用的內(nèi)存。
最早 John McCarthy 在 1959 年左右發(fā)明了垃圾回收,以簡化 Lisp 中的手動(dòng)內(nèi)存管理的機(jī)制(來自 @wikipedia)。
2、為什么要 GC
手動(dòng)管理內(nèi)存挺麻煩,管錯(cuò)或者管漏內(nèi)存也很糟糕,將會(huì)直接導(dǎo)致程序不穩(wěn)定(持續(xù)泄露)甚至直接崩潰。
3、GC 觸發(fā)場景
GC 觸發(fā)的場景主要分為兩大類,分別是:
- 系統(tǒng)觸發(fā):運(yùn)行時(shí)自行根據(jù)內(nèi)置的條件,檢查、發(fā)現(xiàn)到,則進(jìn)行 GC 處理,維護(hù)整個(gè)應(yīng)用程序的可用性。
- 手動(dòng)觸發(fā):開發(fā)者在業(yè)務(wù)代碼中自行調(diào)用
runtime.GC方法來觸發(fā) GC 行為。
3.1系統(tǒng)觸發(fā)
在系統(tǒng)觸發(fā)的場景中,Go 源碼的 src/runtime/mgc.go 文件,明確標(biāo)識(shí)了 GC 系統(tǒng)觸發(fā)的三種場景,分別如下:
const ( gcTriggerHeap gcTriggerKind = iota gcTriggerTime gcTriggerCycle )
gcTriggerHeap:當(dāng)所分配的堆大小達(dá)到閾值(由控制器計(jì)算的觸發(fā)堆的大小)時(shí),將會(huì)觸發(fā)。gcTriggerTime:當(dāng)距離上一個(gè) GC 周期的時(shí)間超過一定時(shí)間時(shí),將會(huì)觸發(fā)。-時(shí)間周期以runtime.forcegcperiod變量為準(zhǔn),默認(rèn) 2 分鐘。gcTriggerCycle:如果沒有開啟 GC,則啟動(dòng) GC。
在手動(dòng)觸發(fā)的 runtime.GC 方法中涉及。
3.2手動(dòng)觸發(fā)
在手動(dòng)觸發(fā)的場景下,Go 語言中僅有 runtime.GC 方法可以觸發(fā),也就沒什么額外的分類的。

但我們要思考的是,一般我們?cè)谑裁礃I(yè)務(wù)場景中,要涉及到手動(dòng)干涉 GC,強(qiáng)制觸發(fā)他呢?
需要手動(dòng)強(qiáng)制觸發(fā)的場景極其少見,可能會(huì)是在某些業(yè)務(wù)方法執(zhí)行完后,因其占用了過多的內(nèi)存,需要人為釋放。又或是 debug 程序所需。
3.3 基本流程
在了解到 Go 語言會(huì)觸發(fā) GC 的場景后,我們進(jìn)一步看看觸發(fā) GC 的流程代碼是怎么樣的,我們可以借助手動(dòng)觸發(fā)的 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 周期前,需要調(diào)用 gcWaitOnMark 方法上一輪 GC 的標(biāo)記結(jié)束(含掃描終止、標(biāo)記、或標(biāo)記終止等)。
開始新的一輪 GC 周期,調(diào)用 gcStart 方法觸發(fā) GC 行為,開始掃描標(biāo)記階段。
需要調(diào)用 gcWaitOnMark 方法等待,直到當(dāng)前 GC 周期的掃描、標(biāo)記、標(biāo)記終止完成。
需要調(diào)用 sweepone 方法,掃描未掃除的堆跨度,并持續(xù)掃除,保證清理完成。在等待掃除完畢前的阻塞時(shí)間,會(huì)調(diào)用 Gosched 讓出。
在本輪 GC 已經(jīng)基本完成后,會(huì)調(diào)用 mProf_PostSweep 方法。以此記錄最后一次標(biāo)記終止時(shí)的堆配置文件快照。
結(jié)束,釋放 M。
3.4 在哪觸發(fā)
看完 GC 的基本流程后,我們有了一個(gè)基本的了解。但可能又有小伙伴有疑惑了?
本文的標(biāo)題是 “GC 什么時(shí)候會(huì)觸發(fā) GC”,雖然我們前面知道了觸發(fā)的時(shí)機(jī)。但是....Go 是哪里實(shí)現(xiàn)的觸發(fā)的機(jī)制,似乎在流程中完全沒有看到?
4、監(jiān)控線程
實(shí)質(zhì)上在 Go 運(yùn)行時(shí)(runtime)初始化時(shí),會(huì)啟動(dòng)一個(gè) goroutine,用于處理 GC 機(jī)制的相關(guān)事項(xiàng)。
代碼如下:
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 sysmon
if debug.gctrace > 0 {
println("GC forced")
}
gcStart(gcTrigger{kind: gcTriggerTime, now: nanotime()})
}
}
在這段程序中,需要特別關(guān)注的是在 forcegchelper 方法中,會(huì)調(diào)用 goparkunlock 方法讓該 goroutine 陷入休眠等待狀態(tài),以減少不必要的資源開銷。
在休眠后,會(huì)由 sysmon 這一個(gè)系統(tǒng)監(jiān)控線程來進(jìn)行監(jiān)控、喚醒等行為:
func sysmon() {
...
for {
...
// check if we need to force a GC
if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 {
lock(&forcegc.lock)
forcegc.idle = 0
var list gList
list.push(forcegc.g)
injectglist(&list)
unlock(&forcegc.lock)
}
if debug.schedtrace > 0 && lasttrace+int64(debug.schedtrace)*1000000 <= now {
lasttrace = now
schedtrace(debug.scheddetail > 0)
}
unlock(&sched.sysmonlock)
}
}
這段代碼核心的行為就是不斷地在 for 循環(huán)中,對(duì) gcTriggerTime 和 now 變量進(jìn)行比較,判斷是否達(dá)到一定的時(shí)間(默認(rèn)為 2 分鐘)。
若達(dá)到意味著滿足條件,會(huì)將 forcegc.g 放到全局隊(duì)列中接受新的一輪調(diào)度,再進(jìn)行對(duì)上面 forcegchelper 的喚醒。
5、堆內(nèi)存申請(qǐng)
在了解定時(shí)觸發(fā)的機(jī)制后,另外一個(gè)場景就是分配的堆空間的時(shí)候,那么我們要看的地方就非常明確了。
那就是運(yùn)行時(shí)申請(qǐng)堆內(nèi)存的 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 = true
span = c.allocLarge(size, needzero, noscan)
...
}
if shouldhelpgc {
if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
gcStart(t)
}
}
return x
}
小對(duì)象:如果申請(qǐng)小對(duì)象時(shí),發(fā)現(xiàn)當(dāng)前內(nèi)存空間不存在空閑跨度時(shí),將會(huì)需要調(diào)用 nextFree 方法獲取新的可用的對(duì)象,可能會(huì)觸發(fā) GC 行為。
大對(duì)象:如果申請(qǐng)大于 32k 以上的大對(duì)象時(shí),可能會(huì)觸發(fā) GC 行為。
總結(jié)
在這篇文章中,我們介紹了 Go 語言觸發(fā) GC 的兩大類場景,并分別基于大類中的細(xì)分場景進(jìn)行了一一說明。
到此這篇關(guān)于談?wù)揋o 什么時(shí)候會(huì)觸發(fā) GC問題的文章就介紹到這了,更多相關(guān)Go 什么時(shí)候會(huì)觸發(fā) GC?內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang編程并發(fā)工具庫MapReduce使用實(shí)踐
這篇文章主要為大家介紹了Golang并發(fā)工具庫MapReduce的使用實(shí)踐,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04
詳解Go開發(fā)Struct轉(zhuǎn)換成map兩種方式比較
本篇文章主要介紹了詳解Go開發(fā)Struct轉(zhuǎn)換成map兩種方式比較,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-07-07
Go語言網(wǎng)站使用異步編程和Goroutine提高Web的性能
作為一門現(xiàn)代化編程語言,Go語言提供了強(qiáng)大的異步編程能力,使得程序員可以以更高效的方式處理并發(fā)任務(wù),在Go語言中,使用Goroutine在單個(gè)進(jìn)程中實(shí)現(xiàn)多任務(wù)并行處理,以及如何使用協(xié)程池來進(jìn)一步提高Web服務(wù)器的處理能力,2024-01-01
Go gin權(quán)限驗(yàn)證實(shí)現(xiàn)過程詳解
這篇文章主要為大家介紹了Go gin權(quán)限驗(yàn)證實(shí)現(xiàn)過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
Golang的select多路復(fù)用及channel使用操作
這篇文章主要介紹了Golang的select多路復(fù)用及channel使用操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12
golang網(wǎng)絡(luò)通信超時(shí)設(shè)置方式
這篇文章主要介紹了golang網(wǎng)絡(luò)通信超時(shí)設(shè)置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12
golang如何使用struct的tag屬性的詳細(xì)介紹
這篇文章主要介紹了golang如何使用struct的tag屬性的詳細(xì)介紹,從例子說起,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-11-11

