Skip to content

Commit 65e1be0

Browse files
committed
update gc
1 parent 283232c commit 65e1be0

File tree

1 file changed

+286
-3
lines changed

1 file changed

+286
-3
lines changed

gc.md

Lines changed: 286 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func setGCPercent(in int32) (out int32) {
8585
}
8686
```
8787

88-
## gcStart
88+
## gc 的调用时机
8989

9090
整个 gc 流程的入口是 gcStart,gcStart 的调用方为:
9191

@@ -105,7 +105,286 @@ forcegchelper --> gcStart
105105

106106
* mallocgc,分配堆内存时触发,会检查当前是否满足触发 gc 的条件,如果触发,那么进入 gcStart。
107107
* forcegchelper,在 forcegchelper 中会把 forcegc.g 这个全局对象的运行 g 挂起。sysmon 会调用 test 检查上次触发 gc 的时间到当前时间是否已经经过了 forcegcperiod 长的时间,如果已经超过,那么就会将 forcegc.g 注入到 globrunq。这样会在该 g 被调度到的时候触发 gc。
108-
* runtime.GC 是由用户主动触发的。
108+
* runtime.GC 是由用户主动触发的,相当于强制触发 GC。
109+
110+
## gcTrigger 和 gc 条件检查
111+
112+
```go
113+
// A gcTrigger is a predicate for starting a GC cycle. Specifically,
114+
// it is an exit condition for the _GCoff phase.
115+
type gcTrigger struct {
116+
kind gcTriggerKind
117+
now int64 // gcTriggerTime: current time
118+
n uint32 // gcTriggerCycle: cycle number to start
119+
}
120+
121+
type gcTriggerKind int
122+
123+
const (
124+
// gcTriggerAlways indicates that a cycle should be started
125+
// unconditionally, even if GOGC is off or we're in a cycle
126+
// right now. This cannot be consolidated with other cycles.
127+
gcTriggerAlways gcTriggerKind = iota
128+
129+
// gcTriggerHeap indicates that a cycle should be started when
130+
// the heap size reaches the trigger heap size computed by the
131+
// controller.
132+
gcTriggerHeap
133+
134+
// gcTriggerTime indicates that a cycle should be started when
135+
// it's been more than forcegcperiod nanoseconds since the
136+
// previous GC cycle.
137+
gcTriggerTime
138+
139+
// gcTriggerCycle indicates that a cycle should be started if
140+
// we have not yet started cycle number gcTrigger.n (relative
141+
// to work.cycles).
142+
gcTriggerCycle
143+
)
144+
```
145+
146+
### mallocgc 中的 trigger 类型是 gcTriggerHeap;
147+
148+
```go
149+
150+
if shouldhelpgc {
151+
if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
152+
gcStart(gcBackgroundMode, t)
153+
}
154+
}
155+
```
156+
157+
### runtime.GC 中使用的是 gcTriggerCycle;
158+
159+
```go
160+
// We're now in sweep N or later. Trigger GC cycle N+1, which
161+
// will first finish sweep N if necessary and then enter sweep
162+
// termination N+1.
163+
gcStart(gcBackgroundMode, gcTrigger{kind: gcTriggerCycle, n: n + 1})
164+
```
165+
166+
### forcegchelper 中使用的是 gcTriggerTime;
167+
168+
```go
169+
// Time-triggered, fully concurrent.
170+
gcStart(gcBackgroundMode, gcTrigger{kind: gcTriggerTime, now: nanotime()})
171+
```
172+
173+
### sysmon 中检查时使用的也是 gcTriggerTime
174+
175+
这里和前面三条不一样的是,这种情况下如果 trigger.test 返回 true,会使用 forcegchelper 所在的 g 来执行 gcStart,具体做法就是上面提到的把 forcegc.g 注入到全局 runq;
176+
177+
```go
178+
// check if we need to force a GC
179+
if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 {
180+
lock(&forcegc.lock)
181+
forcegc.idle = 0
182+
forcegc.g.schedlink = 0
183+
injectglist(forcegc.g)
184+
unlock(&forcegc.lock)
185+
}
186+
```
187+
188+
### trigger.test, gc 条件检查
189+
190+
```go
191+
// test returns true if the trigger condition is satisfied, meaning
192+
// that the exit condition for the _GCoff phase has been met. The exit
193+
// condition should be tested when allocating.
194+
func (t gcTrigger) test() bool {
195+
if !memstats.enablegc || panicking != 0 {
196+
return false
197+
}
198+
if t.kind == gcTriggerAlways {
199+
return true
200+
}
201+
if gcphase != _GCoff {
202+
return false
203+
}
204+
switch t.kind {
205+
case gcTriggerHeap:
206+
// Non-atomic access to heap_live for performance. If
207+
// we are going to trigger on this, this thread just
208+
// atomically wrote heap_live anyway and we'll see our
209+
// own write.
210+
return memstats.heap_live >= memstats.gc_trigger
211+
case gcTriggerTime:
212+
if gcpercent < 0 {
213+
return false
214+
}
215+
lastgc := int64(atomic.Load64(&memstats.last_gc_nanotime))
216+
return lastgc != 0 && t.now-lastgc > forcegcperiod
217+
case gcTriggerCycle:
218+
// t.n > work.cycles, but accounting for wraparound.
219+
return int32(t.n-work.cycles) > 0
220+
}
221+
return true
222+
}
223+
```
224+
225+
## gcStart
226+
227+
```go
228+
// gcStart transitions the GC from _GCoff to _GCmark (if
229+
// !mode.stwMark) or _GCmarktermination (if mode.stwMark) by
230+
// performing sweep termination and GC initialization.
231+
//
232+
// This may return without performing this transition in some cases,
233+
// such as when called on a system stack or with locks held.
234+
func gcStart(mode gcMode, trigger gcTrigger) {
235+
// Since this is called from malloc and malloc is called in
236+
// the guts of a number of libraries that might be holding
237+
// locks, don't attempt to start GC in non-preemptible or
238+
// potentially unstable situations.
239+
mp := acquirem()
240+
if gp := getg(); gp == mp.g0 || mp.locks > 1 || mp.preemptoff != "" {
241+
releasem(mp)
242+
return
243+
}
244+
releasem(mp)
245+
mp = nil
246+
247+
// Pick up the remaining unswept/not being swept spans concurrently
248+
//
249+
// This shouldn't happen if we're being invoked in background
250+
// mode since proportional sweep should have just finished
251+
// sweeping everything, but rounding errors, etc, may leave a
252+
// few spans unswept. In forced mode, this is necessary since
253+
// GC can be forced at any point in the sweeping cycle.
254+
//
255+
// We check the transition condition continuously here in case
256+
// this G gets delayed in to the next GC cycle.
257+
for trigger.test() && gosweepone() != ^uintptr(0) {
258+
sweep.nbgsweep++
259+
}
260+
261+
// Perform GC initialization and the sweep termination
262+
// transition.
263+
semacquire(&work.startSema)
264+
// Re-check transition condition under transition lock.
265+
if !trigger.test() {
266+
semrelease(&work.startSema)
267+
return
268+
}
269+
270+
// For stats, check if this GC was forced by the user.
271+
work.userForced = trigger.kind == gcTriggerAlways || trigger.kind == gcTriggerCycle
272+
273+
// In gcstoptheworld debug mode, upgrade the mode accordingly.
274+
// We do this after re-checking the transition condition so
275+
// that multiple goroutines that detect the heap trigger don't
276+
// start multiple STW GCs.
277+
if mode == gcBackgroundMode {
278+
if debug.gcstoptheworld == 1 {
279+
mode = gcForceMode
280+
} else if debug.gcstoptheworld == 2 {
281+
mode = gcForceBlockMode
282+
}
283+
}
284+
285+
// Ok, we're doing it! Stop everybody else
286+
semacquire(&worldsema)
287+
288+
if trace.enabled {
289+
traceGCStart()
290+
}
291+
292+
if mode == gcBackgroundMode {
293+
gcBgMarkStartWorkers()
294+
}
295+
296+
gcResetMarkState()
297+
298+
work.stwprocs, work.maxprocs = gomaxprocs, gomaxprocs
299+
if work.stwprocs > ncpu {
300+
// This is used to compute CPU time of the STW phases,
301+
// so it can't be more than ncpu, even if GOMAXPROCS is.
302+
work.stwprocs = ncpu
303+
}
304+
work.heap0 = atomic.Load64(&memstats.heap_live)
305+
work.pauseNS = 0
306+
work.mode = mode
307+
308+
now := nanotime()
309+
work.tSweepTerm = now
310+
work.pauseStart = now
311+
if trace.enabled {
312+
traceGCSTWStart(1)
313+
}
314+
systemstack(stopTheWorldWithSema)
315+
// Finish sweep before we start concurrent scan.
316+
systemstack(func() {
317+
finishsweep_m()
318+
})
319+
// clearpools before we start the GC. If we wait they memory will not be
320+
// reclaimed until the next GC cycle.
321+
clearpools()
322+
323+
work.cycles++
324+
if mode == gcBackgroundMode { // Do as much work concurrently as possible
325+
gcController.startCycle()
326+
work.heapGoal = memstats.next_gc
327+
328+
// Enter concurrent mark phase and enable
329+
// write barriers.
330+
//
331+
// Because the world is stopped, all Ps will
332+
// observe that write barriers are enabled by
333+
// the time we start the world and begin
334+
// scanning.
335+
//
336+
// Write barriers must be enabled before assists are
337+
// enabled because they must be enabled before
338+
// any non-leaf heap objects are marked. Since
339+
// allocations are blocked until assists can
340+
// happen, we want enable assists as early as
341+
// possible.
342+
setGCPhase(_GCmark)
343+
344+
gcBgMarkPrepare() // Must happen before assist enable.
345+
gcMarkRootPrepare()
346+
347+
// Mark all active tinyalloc blocks. Since we're
348+
// allocating from these, they need to be black like
349+
// other allocations. The alternative is to blacken
350+
// the tiny block on every allocation from it, which
351+
// would slow down the tiny allocator.
352+
gcMarkTinyAllocs()
353+
354+
// At this point all Ps have enabled the write
355+
// barrier, thus maintaining the no white to
356+
// black invariant. Enable mutator assists to
357+
// put back-pressure on fast allocating
358+
// mutators.
359+
atomic.Store(&gcBlackenEnabled, 1)
360+
361+
// Assists and workers can start the moment we start
362+
// the world.
363+
gcController.markStartTime = now
364+
365+
// Concurrent mark.
366+
systemstack(func() {
367+
now = startTheWorldWithSema(trace.enabled)
368+
})
369+
work.pauseNS += now - work.pauseStart
370+
work.tMark = now
371+
} else {
372+
if trace.enabled {
373+
// Switch to mark termination STW.
374+
traceGCSTWDone()
375+
traceGCSTWStart(0)
376+
}
377+
t := nanotime()
378+
work.tMark, work.tMarkTerm = t, t
379+
work.heapGoal = work.heap0
380+
381+
// Perform mark termination. This will restart the world.
382+
gcMarkTermination(memstats.triggerRatio)
383+
}
384+
385+
semrelease(&work.startSema)
386+
}
387+
```
109388

110389
## runtime.GC
111390

@@ -203,4 +482,8 @@ func GC() {
203482
}
204483
releasem(mp)
205484
}
206-
```
485+
```
486+
487+
## FAQ
488+
489+
为什么需要 debug.FreeOsMemory 才能释放堆空间。

0 commit comments

Comments
 (0)