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}