19// Find the bits for b and the size of the object at b.

20//

21// b is either the beginning of an object, in which case this

22// is the size of the object to scan, or it points to an

23// oblet, in which case we compute the size to scan below.

24// 获取对象对应的bitmap

25hbits := heapBitsForAddr(b)

26// 获取对象所在的span

27s := spanOfUnchecked(b)

28// 获取对象的大小

29n := s.elemsize

30ifn == 0{

31throw( "scanobject n == 0")

32}

33// 对象大小过大时(maxObletBytes是128KB)需要分割扫描

34// 每次最多只扫描128KB

35ifn > maxObletBytes {

36// Large object. Break into oblets for better

37// parallelism and lower latency.

38ifb == s.base {

39// It's possible this is a noscan object (not

40// from greyobject, but from other code

41// paths), in which case we must *not* enqueue

42// oblets since their bitmaps will be

43// uninitialized.

44ifs.spanclass.noscan {

45// Bypass the whole scan.

46gcw.bytesMarked += uint64(n)

47return

48}

49// Enqueue the other oblets to scan later.

50// Some oblets may be in b's scalar tail, but

51// these will be marked as "no more pointers",

52// so we'll drop out immediately when we go to

53// scan those.

54foroblet := b + maxObletBytes; oblet < s.base+s.elemsize; oblet += maxObletBytes {

55if!gcw.putFast(oblet) {

56gcw.put(oblet)

57}

58}

59}

60// Compute the size of the oblet. Since this object

61// must be a large object, s.base is the beginning

62// of the object.

63n = s.base + s.elemsize - b

64ifn > maxObletBytes {

65n = maxObletBytes

66}

67}

68// 扫描对象中的指针

69vari uintptr

70fori = 0; i < n; i += sys.PtrSize {

71// 获取对应的bit

72// Find bits for this word.

73ifi != 0{

74// Avoid needless hbits.next on last iteration.

75hbits = hbits.next

76}

77// Load bits once. See CL 22712 and issue 16973 for discussion.

78bits := hbits.bits

79// 检查scan bit判断是否继续扫描, 注意第二个scan bit是checkmark

80// During checkmarking, 1-word objects store the checkmark

81// in the type bit for the one word. The only one-word objects

82// are pointers, or else they'd be merged with other non-pointer

83// data into larger allocations.

84ifi != 1*sys.PtrSize && bits&bitScan == 0{

85break// no more pointers in this object

86}

87// 检查pointer bit, 不是指针则继续

88ifbits&bitPointer == 0{

89continue// not a pointer

90}

91// 取出指针的值

92// Work here is duplicated in scanblock and above.

93// If you make changes here, make changes there too.

94obj := *(* uintptr)(unsafe.Pointer(b + i))

95// 如果指针在arena区域中, 则调用greyobject标记对象并把对象放到标记队列中

96// At this point we have extracted the next potential pointer.

97// Check if it points into heap and not back at the current object.

98ifobj != 0&& arena_start <= obj && obj < arena_used && obj-b >= n {

99// Mark the object.

100ifobj, hbits, span, objIndex := heapBitsForObject(obj, b, i); obj != 0{

101greyobject(obj, b, i, hbits, span, gcw, objIndex)

102}

103}

104}

105// 统计扫描过的大小和对象数量

106gcw.bytesMarked += uint64(n)

107gcw.scanWork += int64(i)

108}

在所有后台标记任务都把标记队列消费完毕时, 会执行gcMarkDone 函数准备进入完成标记阶段(mark termination):

在并行GC中gcMarkDone会被执行两次, 第一次会禁止本地标记队列然后重新开始后台标记任务, 第二次会进入完成标记阶段(mark termination)。

1// gcMarkDone transitions the GC from mark 1 to mark 2 and from mark 2

2// to mark termination.

3//

4// This should be called when all mark work has been drained. In mark

5// 1, this includes all root marking jobs, global work buffers, and

6// active work buffers in assists and background workers; however,

7// work may still be cached in per-P work buffers. In mark 2, per-P

8// caches are disabled.

9//

10// The calling context must be preemptible.

11//

12// Note that it is explicitly okay to have write barriers in this

13// function because completion of concurrent mark is best-effort

14// anyway. Any work created by write barriers here will be cleaned up

15// by mark termination.

16funcgcMarkDone{

17top:

18semacquire(&work.markDoneSema)

19// Re-check transition condition under transition lock.

20if!(gcphase == _GCmark && work.nwait == work.nproc && !gcMarkWorkAvailable( nil)) {

21semrelease(&work.markDoneSema)

22return

23}

24// 暂时禁止启动新的后台标记任务

25// Disallow starting new workers so that any remaining workers

26// in the current mark phase will drain out.

27//

28// TODO(austin): Should dedicated workers keep an eye on this

29// and exit gcDrain promptly?

30atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, -0xffffffff)

31atomic.Xaddint64(&gcController.fractionalMarkWorkersNeeded, -0xffffffff)

32// 判断本地标记队列是否已禁用

33if!gcBlackenPromptly {

34// 本地标记队列是否未禁用, 禁用然后重新开始后台标记任务

35// Transition from mark 1 to mark 2.

36//

37// The global work list is empty, but there can still be work

38// sitting in the per-P work caches.

39// Flush and disable work caches.

40// 禁用本地标记队列

41// Disallow caching workbufs and indicate that we're in mark 2.

42gcBlackenPromptly = true

43// Prevent completion of mark 2 until we've flushed

44// cached workbufs.

45atomic.Xadd(&work.nwait, -1)

46// GC is set up for mark 2. Let Gs blocked on the

47// transition lock go while we flush caches.

48semrelease(&work.markDoneSema)

49// 把所有本地标记队列中的对象都推到全局标记队列

50systemstack( func{

51// Flush all currently cached workbufs and

52// ensure all Ps see gcBlackenPromptly. This

53// also blocks until any remaining mark 1

54// workers have exited their loop so we can

55// start new mark 2 workers.

56forEachP( func(_p_ *p){

57_p_.gcw.dispose

58})

59})

60// 除错用

61// Check that roots are marked. We should be able to

62// do this before the forEachP, but based on issue

63// #16083 there may be a (harmless) race where we can

64// enter mark 2 while some workers are still scanning

65// stacks. The forEachP ensures these scans are done.

66//

67// TODO(austin): Figure out the race and fix this

68// properly.

69gcMarkRootCheck

70// 允许启动新的后台标记任务

71// Now we can start up mark 2 workers.

72atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, 0xffffffff)

73atomic.Xaddint64(&gcController.fractionalMarkWorkersNeeded, 0xffffffff)

74// 如果确定没有更多的任务则可以直接跳到函数顶部

75// 这样就当作是第二次调用了

76incnwait := atomic.Xadd(&work.nwait, + 1)

77ifincnwait == work.nproc && !gcMarkWorkAvailable( nil) {

78// This loop will make progress because

79// gcBlackenPromptly is now true, so it won't

80// take this same "if" branch.

81gototop

82}

83} else{

84// 记录完成标记阶段开始的时间和STW开始的时间

85// Transition to mark termination.

86now := nanotime

87work.tMarkTerm = now

88work.pauseStart = now

89// 禁止G被抢占

90getg.m.preemptoff = "gcing"

91// 停止所有运行中的G, 并禁止它们运行

92systemstack(stopTheWorldWithSema)

93// !!!!!!!!!!!!!!!!

94// 世界已停止(STW)...

95// !!!!!!!!!!!!!!!!

96// The gcphase is _GCmark, it will transition to _GCmarktermination

97// below. The important thing is that the wb remains active until

98// all marking is complete. This includes writes made by the GC.

99// 标记对根对象的扫描已完成, 会影响gcMarkRootPrepare中的处理

100// Record that one root marking pass has completed.

101work.markrootDone = true

102// 禁止辅助GC和后台标记任务的运行

103// Disable assists and background workers. We must do

104// this before waking blocked assists.

105atomic.Store(&gcBlackenEnabled, 0)

106// 唤醒所有因为辅助GC而休眠的G

107// Wake all blocked assists. These will run when we

108// start the world again.

109gcWakeAllAssists

110// Likewise, release the transition lock. Blocked

111// workers and assists will run when we start the

112// world again.

113semrelease(&work.markDoneSema)

114// 计算下一次触发gc需要的heap大小

115// endCycle depends on all gcWork cache stats being

116// flushed. This is ensured by mark 2.

117nextTriggerRatio := gcController.endCycle

118// 进入完成标记阶段, 会重新启动世界

119// Perform mark termination. This will restart the world.

120gcMarkTermination(nextTriggerRatio)

121}

122}

gcMarkTermination 函数会进入完成标记阶段:

1funcgcMarkTermination(nextTriggerRatio float64) {

2// World is stopped.

3// Start marktermination which includes enabling the write barrier.

4// 禁止辅助GC和后台标记任务的运行

5atomic.Store(&gcBlackenEnabled, 0)

6// 重新允许本地标记队列(下次GC使用)

7gcBlackenPromptly = false

8// 设置当前GC阶段到完成标记阶段, 并启用写屏障

9setGCPhase(_GCmarktermination)

10// 记录开始时间

11work.heap1 = memstats.heap_live

12startTime := nanotime

13// 禁止G被抢占

14mp := acquirem

15mp.preemptoff = "gcing"

16_g_ := getg

17_g_.m.traceback = 2

18// 设置G的状态为等待中这样它的栈可以被扫描

19gp := _g_.m.curg

20casgstatus(gp, _Grunning, _Gwaiting)

21gp.waitreason = "garbage collection"

22// 切换到g0运行

23// Run gc on the g0 stack. We do this so that the g stack

24// we're currently running on will no longer change. Cuts

25// the root set down a bit (g0 stacks are not scanned, and

26// we don't need to scan gc's internal state). We also

27// need to switch to g0 so we can shrink the stack.

28systemstack( func{

29// 开始STW中的标记

30gcMark(startTime)

31// 必须立刻返回, 因为外面的G的栈有可能被移动, 不能在这之后访问外面的变量

32// Must return immediately.

33// The outer function's stack may have moved

34// during gcMark (it shrinks stacks, including the

35// outer function's stack), so we must not refer

36// to any of its variables. Return back to the

37// non-system stack to pick up the new addresses

38// before continuing.

39})

40// 重新切换到g0运行

41systemstack( func{

42work.heap2 = work.bytesMarked

43// 如果启用了checkmark则执行检查, 检查是否所有可到达的对象都有标记

44ifdebug.gccheckmark > 0{

45// Run a full stop-the-world mark using checkmark bits,

46// to check that we didn't forget to mark anything during

47// the concurrent mark process.

48gcResetMarkState

49initCheckmarks

50gcMark(startTime)

51clearCheckmarks

52}

53// 设置当前GC阶段到关闭, 并禁用写屏障

54// marking is complete so we can turn the write barrier off

55setGCPhase(_GCoff)

56// 唤醒后台清扫任务, 将在STW结束后开始运行

57gcSweep(work.mode)

58// 除错用

59ifdebug.gctrace > 1{

60startTime = nanotime

61// The g stacks have been scanned so

62// they have gcscanvalid==true and gcworkdone==true.

63// Reset these so that all stacks will be rescanned.

64gcResetMarkState

65finishsweep_m

66// Still in STW but gcphase is _GCoff, reset to _GCmarktermination

67// At this point all objects will be found during the gcMark which

68// does a complete STW mark and object scan.

69setGCPhase(_GCmarktermination)

70gcMark(startTime)

71setGCPhase(_GCoff) // marking is done, turn off wb.

72gcSweep(work.mode)

73}

74})

75// 设置G的状态为运行中

76_g_.m.traceback = 0

77casgstatus(gp, _Gwaiting, _Grunning)

78// 跟踪处理

79iftrace.enabled {

80traceGCDone

81}

82// all done

83mp.preemptoff = ""

84ifgcphase != _GCoff {

85throw( "gc done but gcphase != _GCoff")

86}

87// 更新下一次触发gc需要的heap大小(gc_trigger)

88// Update GC trigger and pacing for the next cycle.

89gcSetTriggerRatio(nextTriggerRatio)

90// 更新用时记录

91// Update timing memstats

92now := nanotime

93sec, nsec, _ := time_now

94unixNow := sec* 1e9+ int64(nsec)

95work.pauseNS += now - work.pauseStart

96work.tEnd = now

97atomic.Store64(&memstats.last_gc_unix, uint64(unixNow)) // must be Unix time to make sense to user

98atomic.Store64(&memstats.last_gc_nanotime, uint64(now)) // monotonic time for us

99memstats.pause_ns[memstats.numgc% uint32( len(memstats.pause_ns))] = uint64(work.pauseNS)

100memstats.pause_end[memstats.numgc% uint32( len(memstats.pause_end))] = uint64(unixNow)

101memstats.pause_total_ns += uint64(work.pauseNS)

102// 更新所用cpu记录

103// Update work.totaltime.

104sweepTermCpu := int64(work.stwprocs) * (work.tMark - work.tSweepTerm)

105// We report idle marking time below, but omit it from the

106// overall utilization here since it's "free".

107markCpu := gcController.assistTime + gcController.dedicatedMarkTime + gcController.fractionalMarkTime

108markTermCpu := int64(work.stwprocs) * (work.tEnd - work.tMarkTerm)

109cycleCpu := sweepTermCpu + markCpu + markTermCpu

110work.totaltime += cycleCpu

111// Compute overall GC CPU utilization.

112totalCpu := sched.totaltime + (now-sched.procresizetime)* int64(gomaxprocs)

113memstats.gc_cpu_fraction = float64(work.totaltime) / float64(totalCpu)

114// 重置清扫状态

115// Reset sweep state.

116sweep.nbgsweep = 0

117sweep.npausesweep = 0

118// 统计强制开始GC的次数

119ifwork.userForced {

120memstats.numforcedgc++

121}

122// 统计执行GC的次数然后唤醒等待清扫的G

123// Bump GC cycle count and wake goroutines waiting on sweep.

124lock(&work.sweepWaiters.lock)

125memstats.numgc++

126injectglist(work.sweepWaiters.head.ptr)

127work.sweepWaiters.head = 0

128unlock(&work.sweepWaiters.lock)

129// 性能统计用

130// Finish the current heap profiling cycle and start a new

131// heap profiling cycle. We do this before starting the world

132// so events don't leak into the wrong cycle.

133mProf_NextCycle

134// 重新启动世界

135systemstack(startTheWorldWithSema)

136// !!!!!!!!!!!!!!!

137// 世界已重新启动...

138// !!!!!!!!!!!!!!!

139// 性能统计用

140// Flush the heap profile so we can start a new cycle next GC.

141// This is relatively expensive, so we don't do it with the

142// world stopped.

143mProf_Flush

144// 移动标记队列使用的缓冲区到自由列表, 使得它们可以被回收

145// Prepare workbufs for freeing by the sweeper. We do this

146// asynchronously because it can take non-trivial time.

147prepareFreeWorkbufs

148// 释放未使用的栈

149// Free stack spans. This must be done between GC cycles.

150systemstack(freeStackSpans)

151// 除错用

152// Print gctrace before dropping worldsema. As soon as we drop

153// worldsema another cycle could start and smash the stats

154// we're trying to print.

155ifdebug.gctrace > 0{

156util := int(memstats.gc_cpu_fraction * 100)

157varsbuf [ 24] byte

158printlock

159print( "gc ", memstats.numgc,

160" @", string(itoaDiv(sbuf[:], uint64(work.tSweepTerm-runtimeInitTime)/ 1e6, 3)), "s ",

161util, "%: ")

162prev := work.tSweepTerm

163fori, ns := range[] int64{work.tMark, work.tMarkTerm, work.tEnd} {

164ifi != 0{

165print( "+")

166}

167print( string(fmtNSAsMS(sbuf[:], uint64(ns-prev))))

168prev = ns

169}

170print( " ms clock, ")

171fori, ns := range[] int64{sweepTermCpu, gcController.assistTime, gcController.dedicatedMarkTime + gcController.fractionalMarkTime, gcController.idleMarkTime, markTermCpu} {

172ifi == 2|| i == 3{

173// Separate mark time components with /.

174print( "/")

175} elseifi != 0{

176print( "+")

177}

178print( string(fmtNSAsMS(sbuf[:], uint64(ns))))

179}

180print( " ms cpu, ",

181work.heap0>> 20, "->", work.heap1>> 20, "->", work.heap2>> 20, " MB, ",

182work.heapGoal>> 20, " MB goal, ",

183work.maxprocs, " P")

184ifwork.userForced {

185print( " (forced)")

186}

187print( "n")

188printunlock

189}

190semrelease(&worldsema)

191// Careful: another GC cycle may start now.

192// 重新允许当前的G被抢占

193releasem(mp)

194mp = nil

195// 如果是并行GC, 让当前M继续运行(会回到gcBgMarkWorker然后休眠)

196// 如果不是并行GC, 则让当前M开始调度

197// now that gc is done, kick off finalizer thread if needed

198if!concurrentSweep {

199// give the queued finalizers, if any, a chance to run

200Gosched

201}

202}

gcSweep 函数会唤醒后台清扫任务:

后台清扫任务会在程序启动时调用的gcenable 函数中启动.

1func gcSweep( mode gcMode) {

2ifgcphase != _GCoff {

3throw( "gcSweep being done but phase is not GCoff")

4}

5// 增加sweepgen, 这样sweepSpans中两个队列角色会交换, 所有span都会变为"待清扫"的span

6lock(&mheap_. lock)

7mheap_.sweepgen += 2

8mheap_.sweepdone = 0

9ifmheap_.sweepSpans[mheap_.sweepgen/ 2% 2].index != 0{

10// We should have drained this list during the last

11// sweep phase. We certainly need to start this phase

12// with an empty swept list.

13throw( "non-empty swept list")

14}

15mheap_.pagesSwept = 0

16unlock(&mheap_. lock)

17// 如果非并行GC则在这里完成所有工作(STW中)

18if!_ConcurrentSweep || mode == gcForceBlockMode {

19// Special case synchronous sweep.

20// Record that no proportional sweeping has to happen.

21lock(&mheap_. lock)

22mheap_.sweepPagesPerByte = 0

23unlock(&mheap_. lock)

24// Sweep all spans eagerly.

25forsweepone( ) ! = ^uintptr( 0) {

26sweep.npausesweep++

27}

28// Free workbufs eagerly.

29prepareFreeWorkbufs

30forfreeSomeWbufs( false) {

31}

32// All "free" events for this mark/sweep cycle have

33// now happened, so we can make this profile cycle

34// available immediately.

35mProf_NextCycle

36mProf_Flush

37return

38}

39// 唤醒后台清扫任务

40// Background sweep.

41lock(&sweep. lock)

42ifsweep.parked {

43sweep.parked = false

44ready( sweep.g, 0, true)

45}

46unlock( &sweep. lock)

47}

48

后台清扫任务的函数是bgsweep :

1func bgsweep( c chan int) {

2sweep.g = getg

3// 等待唤醒

4lock(&sweep. lock)

5sweep.parked = true

6c <- 1

7goparkunlock(&sweep. lock, "GC sweep wait", traceEvGoBlock, 1)

8// 循环清扫

9for{

10// 清扫一个span, 然后进入调度(一次只做少量工作)

11forgosweepone( ) ! = ^uintptr( 0) {

12sweep.nbgsweep++

13Gosched

14}

15// 释放一些未使用的标记队列缓冲区到heap

16forfreeSomeWbufs( true) {

17Gosched

18}

19// 如果清扫未完成则继续循环

20lock(&sweep. lock)

21if!gosweepdone {

22// This can happen if a GC runs between

23// gosweepone returning ^0 above

24// and the lock being acquired.

25unlock(&sweep. lock)

26continue

27}

28// 否则让后台清扫任务进入休眠, 当前M继续调度

29sweep.parked = true

30goparkunlock( &sweep. lock, "GC sweep wait", traceEvGoBlock, 1)

31}

32}

33

gosweepone 函数会从sweepSpans中取出单个span清扫:

1//go:nowritebarrier

2funcgosweeponeuintptr{

3varret uintptr

4// 切换到g0运行

5systemstack( func{

6ret = sweepone

7})

8returnret

9}

sweepone 函数如下:

1// sweeps one span

2// returns number of pages returned to heap, or ^uintptr(0) if there is nothing to sweep

3//go:nowritebarrier

4funcsweeponeuintptr{

5_g_ := getg

6sweepRatio := mheap_.sweepPagesPerByte // For debugging

7// 禁止G被抢占

8// increment locks to ensure that the goroutine is not preempted

9// in the middle of sweep thus leaving the span in an inconsistent state for next GC

10_g_.m.locks++

11// 检查是否已完成清扫

12ifatomic.Load(&mheap_.sweepdone) != 0{

13_g_.m.locks--

14return^ uintptr( 0)

15}

16// 更新同时执行sweep的任务数量

17atomic.Xadd(&mheap_.sweepers, + 1)

18npages := ^ uintptr( 0)

19sg := mheap_.sweepgen

20for{

21// 从sweepSpans中取出一个span

22s := mheap_.sweepSpans[ 1-sg/ 2% 2].pop

23// 全部清扫完毕时跳出循环

24ifs == nil{

25atomic.Store(&mheap_.sweepdone, 1)

26break

27}

28// 其他M已经在清扫这个span时跳过

29ifs.state != mSpanInUse {

30// This can happen if direct sweeping already

31// swept this span, but in that case the sweep

32// generation should always be up-to-date.

33ifs.sweepgen != sg {

34print( "runtime: bad span s.state=", s.state, " s.sweepgen=", s.sweepgen, " sweepgen=", sg, "n")

35throw( "non in-use span in unswept list")

36}

37continue

38}

39// 原子增加span的sweepgen, 失败表示其他M已经开始清扫这个span, 跳过

40ifs.sweepgen != sg -2|| !atomic.Cas(&s.sweepgen, sg -2, sg -1) {

41continue

42}

43// 清扫这个span, 然后跳出循环

44npages = s.npages

45if!s.sweep( false) {

46// Span is still in-use, so this returned no

47// pages to the heap and the span needs to

48// move to the swept in-use list.

49npages = 0

50}

51break

52}

53// 更新同时执行sweep的任务数量

54// Decrement the number of active sweepers and if this is the

55// last one print trace information.

56ifatomic.Xadd(&mheap_.sweepers, -1) == 0&& atomic.Load(&mheap_.sweepdone) != 0{

57ifdebug.gcpacertrace > 0{

58print( "pacer: sweep done at heap size ", memstats.heap_live>> 20, "MB; allocated ", (memstats.heap_live-mheap_.sweepHeapLiveBasis)>> 20, "MB during sweep; swept ", mheap_.pagesSwept, " pages at ", sweepRatio, " pages/byten")

59}

60}

61// 允许G被抢占

62_g_.m.locks--

63// 返回清扫的页数

64returnnpages

65}