自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go 什么時(shí)候會(huì)觸發(fā) GC?

開(kāi)發(fā) 后端
Go 語(yǔ)言作為一門(mén)新語(yǔ)言,在早期經(jīng)常遭到唾棄的就是在垃圾回收(下稱(chēng):GC)機(jī)制中 STW(Stop-The-World)的時(shí)間過(guò)長(zhǎng)。

[[426528]]

本文轉(zhuǎn)載自微信公眾號(hào)「腦子進(jìn)煎魚(yú)了」,作者陳煎魚(yú) 。轉(zhuǎn)載本文請(qǐng)聯(lián)系腦子進(jìn)煎魚(yú)了公眾號(hào)。

大家好,我是煎魚(yú)。

Go 語(yǔ)言作為一門(mén)新語(yǔ)言,在早期經(jīng)常遭到唾棄的就是在垃圾回收(下稱(chēng):GC)機(jī)制中 STW(Stop-The-World)的時(shí)間過(guò)長(zhǎng)。

那么這個(gè)時(shí)候,我們又會(huì)好奇一點(diǎn),作為 STW 的起始,Go 語(yǔ)言中什么時(shí)候才會(huì)觸發(fā) GC 呢?

今天就由煎魚(yú)帶大家一起來(lái)學(xué)習(xí)研討一輪。

什么是 GC

在計(jì)算機(jī)科學(xué)中,垃圾回收(GC)是一種自動(dòng)管理內(nèi)存的機(jī)制,垃圾回收器會(huì)去嘗試回收程序不再使用的對(duì)象及其占用的內(nèi)存。

最早 John McCarthy 在 1959 年左右發(fā)明了垃圾回收,以簡(jiǎn)化 Lisp 中的手動(dòng)內(nèi)存管理的機(jī)制(來(lái)自 @wikipedia)。

為什么要 GC

手動(dòng)管理內(nèi)存挺麻煩,管錯(cuò)或者管漏內(nèi)存也很糟糕,將會(huì)直接導(dǎo)致程序不穩(wěn)定(持續(xù)泄露)甚至直接崩潰。

GC 觸發(fā)場(chǎng)景

GC 觸發(fā)的場(chǎng)景主要分為兩大類(lèi),分別是:

  • 系統(tǒng)觸發(fā):運(yùn)行時(shí)自行根據(jù)內(nèi)置的條件,檢查、發(fā)現(xiàn)到,則進(jìn)行 GC 處理,維護(hù)整個(gè)應(yīng)用程序的可用性。
  • 手動(dòng)觸發(fā):開(kāi)發(fā)者在業(yè)務(wù)代碼中自行調(diào)用 runtime.GC 方法來(lái)觸發(fā) GC 行為。

系統(tǒng)觸發(fā)

在系統(tǒng)觸發(fā)的場(chǎng)景中,Go 源碼的 src/runtime/mgc.go 文件,明確標(biāo)識(shí)了 GC 系統(tǒng)觸發(fā)的三種場(chǎng)景,分別如下:

  1. const ( 
  2.  gcTriggerHeap gcTriggerKind = iota 
  3.  gcTriggerTime 
  4.  gcTriggerCycle 
  • gcTriggerHeap:當(dāng)所分配的堆大小達(dá)到閾值(由控制器計(jì)算的觸發(fā)堆的大小)時(shí),將會(huì)觸發(fā)。
  • gcTriggerTime:當(dāng)距離上一個(gè) GC 周期的時(shí)間超過(guò)一定時(shí)間時(shí),將會(huì)觸發(fā)。-時(shí)間周期以 runtime.forcegcperiod 變量為準(zhǔn),默認(rèn) 2 分鐘。
  • gcTriggerCycle:如果沒(méi)有開(kāi)啟 GC,則啟動(dòng) GC。

在手動(dòng)觸發(fā)的 runtime.GC 方法中涉及。

手動(dòng)觸發(fā)

在手動(dòng)觸發(fā)的場(chǎng)景下,Go 語(yǔ)言中僅有 runtime.GC 方法可以觸發(fā),也就沒(méi)什么額外的分類(lèi)的。

但我們要思考的是,一般我們?cè)谑裁礃I(yè)務(wù)場(chǎng)景中,要涉及到手動(dòng)干涉 GC,強(qiáng)制觸發(fā)他呢?

需要手動(dòng)強(qiáng)制觸發(fā)的場(chǎng)景極其少見(jiàn),可能會(huì)是在某些業(yè)務(wù)方法執(zhí)行完后,因其占用了過(guò)多的內(nèi)存,需要人為釋放。又或是 debug 程序所需。

基本流程

在了解到 Go 語(yǔ)言會(huì)觸發(fā) GC 的場(chǎng)景后,我們進(jìn)一步看看觸發(fā) GC 的流程代碼是怎么樣的,我們可以借助手動(dòng)觸發(fā)的 runtime.GC 方法來(lái)作為突破口。

核心代碼如下:

  1. func GC() { 
  2.  n := atomic.Load(&work.cycles) 
  3.  gcWaitOnMark(n) 
  4.  
  5.  gcStart(gcTrigger{kind: gcTriggerCycle, n: n + 1}) 
  6.    
  7.  gcWaitOnMark(n + 1) 
  8.  
  9.  for atomic.Load(&work.cycles) == n+1 && sweepone() != ^uintptr(0) { 
  10.   sweep.nbgsweep++ 
  11.   Gosched() 
  12.  } 
  13.    
  14.  for atomic.Load(&work.cycles) == n+1 && atomic.Load(&mheap_.sweepers) != 0 { 
  15.   Gosched() 
  16.  } 
  17.    
  18.  mp := acquirem() 
  19.  cycle := atomic.Load(&work.cycles) 
  20.  if cycle == n+1 || (gcphase == _GCmark && cycle == n+2) { 
  21.   mProf_PostSweep() 
  22.  } 
  23.  releasem(mp) 

在開(kāi)始新的一輪 GC 周期前,需要調(diào)用 gcWaitOnMark 方法上一輪 GC 的標(biāo)記結(jié)束(含掃描終止、標(biāo)記、或標(biāo)記終止等)。

開(kāi)始新的一輪 GC 周期,調(diào)用 gcStart 方法觸發(fā) GC 行為,開(kāi)始掃描標(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。

在哪觸發(fā)

看完 GC 的基本流程后,我們有了一個(gè)基本的了解。但可能又有小伙伴有疑惑了?

本文的標(biāo)題是 “GC 什么時(shí)候會(huì)觸發(fā) GC”,雖然我們前面知道了觸發(fā)的時(shí)機(jī)。但是....Go 是哪里實(shí)現(xiàn)的觸發(fā)的機(jī)制,似乎在流程中完全沒(méi)有看到?

監(jiān)控線程

實(shí)質(zhì)上在 Go 運(yùn)行時(shí)(runtime)初始化時(shí),會(huì)啟動(dòng)一個(gè) goroutine,用于處理 GC 機(jī)制的相關(guān)事項(xiàng)。

代碼如下:

  1. func init() { 
  2.  go forcegchelper() 
  3.  
  4. func forcegchelper() { 
  5.  forcegc.g = getg() 
  6.  lockInit(&forcegc.lock, lockRankForcegc) 
  7.  for { 
  8.   lock(&forcegc.lock) 
  9.   if forcegc.idle != 0 { 
  10.    throw("forcegc: phase error"
  11.   } 
  12.   atomic.Store(&forcegc.idle, 1) 
  13.   goparkunlock(&forcegc.lock, waitReasonForceGCIdle, traceEvGoBlock, 1) 
  14.     // this goroutine is explicitly resumed by sysmon 
  15.   if debug.gctrace > 0 { 
  16.    println("GC forced"
  17.   } 
  18.  
  19.   gcStart(gcTrigger{kind: gcTriggerTime, now: nanotime()}) 
  20.  } 

在這段程序中,需要特別關(guān)注的是在 forcegchelper 方法中,會(huì)調(diào)用 goparkunlock 方法讓該 goroutine 陷入休眠等待狀態(tài),以減少不必要的資源開(kāi)銷(xiāo)。

在休眠后,會(huì)由 sysmon 這一個(gè)系統(tǒng)監(jiān)控線程來(lái)進(jìn)行監(jiān)控、喚醒等行為:

  1. func sysmon() { 
  2.  ... 
  3.  for { 
  4.   ... 
  5.   // check if we need to force a GC 
  6.   if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 { 
  7.    lock(&forcegc.lock) 
  8.    forcegc.idle = 0 
  9.    var list gList 
  10.    list.push(forcegc.g) 
  11.    injectglist(&list) 
  12.    unlock(&forcegc.lock) 
  13.   } 
  14.   if debug.schedtrace > 0 && lasttrace+int64(debug.schedtrace)*1000000 <= now { 
  15.    lasttrace = now 
  16.    schedtrace(debug.scheddetail > 0) 
  17.   } 
  18.   unlock(&sched.sysmonlock) 
  19.  } 

這段代碼核心的行為就是不斷地在 for 循環(huán)中,對(duì) gcTriggerTime 和 now 變量進(jìn)行比較,判斷是否達(dá)到一定的時(shí)間(默認(rèn)為 2 分鐘)。

若達(dá)到意味著滿足條件,會(huì)將 forcegc.g 放到全局隊(duì)列中接受新的一輪調(diào)度,再進(jìn)行對(duì)上面 forcegchelper 的喚醒。

堆內(nèi)存申請(qǐng)

在了解定時(shí)觸發(fā)的機(jī)制后,另外一個(gè)場(chǎng)景就是分配的堆空間的時(shí)候,那么我們要看的地方就非常明確了。

那就是運(yùn)行時(shí)申請(qǐng)堆內(nèi)存的 mallocgc 方法。核心代碼如下:

  1. func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { 
  2.  shouldhelpgc := false 
  3.  ... 
  4.  if size <= maxSmallSize { 
  5.   if noscan && size < maxTinySize { 
  6.    ... 
  7.    // Allocate a new maxTinySize block. 
  8.    span = c.alloc[tinySpanClass] 
  9.    v := nextFreeFast(span) 
  10.    if v == 0 { 
  11.     v, span, shouldhelpgc = c.nextFree(tinySpanClass) 
  12.    } 
  13.    ... 
  14.    spc := makeSpanClass(sizeclass, noscan) 
  15.    span = c.alloc[spc] 
  16.    v := nextFreeFast(span) 
  17.    if v == 0 { 
  18.     v, span, shouldhelpgc = c.nextFree(spc) 
  19.    } 
  20.    ... 
  21.   } 
  22.  } else { 
  23.   shouldhelpgc = true 
  24.   span = c.allocLarge(size, needzero, noscan) 
  25.   ... 
  26.  } 
  27.  
  28.  if shouldhelpgc { 
  29.   if t := (gcTrigger{kind: gcTriggerHeap}); t.test() { 
  30.    gcStart(t) 
  31.   } 
  32.  } 
  33.  
  34.  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 語(yǔ)言觸發(fā) GC 的兩大類(lèi)場(chǎng)景,并分別基于大類(lèi)中的細(xì)分場(chǎng)景進(jìn)行了一一說(shuō)明。

一般來(lái)講,我們對(duì)其了解大概就可以了。若小伙伴們對(duì)其內(nèi)部具體實(shí)現(xiàn)感興趣,也可以以文章中的代碼具體再打開(kāi)看。

但需要注意,很有可能 Go 版本一升級(jí),可能又變了,學(xué)思想要緊!

 

責(zé)任編輯:武曉燕 來(lái)源: 腦子進(jìn)煎魚(yú)了
相關(guān)推薦

2024-10-29 08:52:01

Go協(xié)作式調(diào)度

2021-04-19 09:20:01

Go 搶占 P語(yǔ)言

2015-03-02 14:44:48

AngularJS jQuery超越

2023-06-06 16:54:00

2025-02-28 09:04:08

2023-02-01 15:49:51

人工智能AI

2020-05-12 11:25:50

MySQLES數(shù)據(jù)庫(kù)

2017-05-15 09:55:07

2015-07-08 15:55:01

NSStringcopystrong

2013-11-28 16:03:24

2012-09-24 10:20:39

JavaScriptJS

2017-06-28 15:06:51

PythonLambda函數(shù)

2022-05-19 10:27:34

機(jī)器學(xué)習(xí)人工智能

2024-08-05 01:22:16

2021-08-13 11:31:23

HTTP

2021-01-30 19:59:37

性能項(xiàng)目開(kāi)源

2015-02-01 09:45:46

2017-04-05 21:43:08

MQ互聯(lián)網(wǎng)架構(gòu)

2011-10-18 16:41:23

編程

2012-07-26 10:27:31

PHP
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)