golang系列之-垃圾回收

go的垃圾回收原理官方说法是是三色标记法+混合写屏障,但是,理论上怎么说是一回事,具体实现又是另一回事了,或者说实现已经偏离文档描述。总的来说,GC这部份的内容要比GMP跟内存分配都要复杂的多且出人意料。当前go版本:1.24

前言

目标对象

Go的GC并不扫描整个内存,只关注以下区域:

  1. heap上分配的对象(在mspan管理范围)
  2. data/bss段的全局变量
  3. 栈上的引用(扫描stackRoots)

触发类型

触发类型 备注
gcTriggerHeap 堆大小触发,heap内存达到一个临界点触发(最常用)
gcTriggerTime 时间触发,超时2min未执行GC则强制执行
gcTriggerCycle 手动触发,用户调用runtime.GC()

角色/分工

角色 作用 工作阶段 备注
Mark Worker(标记工作线程) 遍历对象图,标记存活对象,防止被错误回收 标记阶段(Marking) 运行时创建多个并发 Mark Worker,加快标记速度
Sweeper(清扫器) 清理未标记的对象,将其内存释放回空闲列表(mheap.free) 清扫阶段(Sweeping) 逐步清理,避免一次性 STW(Stop The World)
Scavenger(内存回收器) 释放长期未使用的堆内存,归还给OS以减少RSS 后台运行(定期触发) 主要针对大对象或空闲mspans,减少物理内存占用

标记工作线程

其中,标记工作线程的会根据工作模式进一步区分,如下

标记工作线程模式 备注
gcMarkWorkerNotWorker 默认,未运行
gcMarkWorkerDedicatedMode 专用标记任务,最高优先级,非抢占
gcMarkWorkerFractionalMode 比例标记任务,跟其他g共享时间,可被抢占
gcMarkWorkerIdleMode 空闲时执行的低优先级标记任务,需要p空闲

CPU限制

标记工作线程的CPU使用率被限制在25%(GOGC=100时),假设系统使用一个6核CPU,那么GC在标记阶段大约会使用1.5个CPU资源:

  1. 1个CPU由Dedicated(专用模式)的标记工作线程持续占用,该线程不允许抢占,直到标记任务完成
  2. 0.5个CPU由Fractional(比例模式)的标记工作线程使用,该线程仅在由额外CPU资源可用时运行,并会根据系统负责动态调整自身的CPU使用率

完整运行流程

  1. Sweep Termination(清理终止)

    • STW(Stop The World),确保所有P都达到GC安全点
    • 完成上一轮GC未完成的sweep(清扫),回收剩余的的mspan
    • 准备GC统计数据,为新一轮的GC计算目标heap大小、触发阈值等
  2. Mark(标记)

    • STW,切换GC状态
      • gcphase从_GCoff切换到_GCmark
      • 开启写屏障(write barrier),允许Mutator协助GC标记,以维护三色标记不变性
      • 启动GC后台线程,执行并发标记任务
      • 根对象入队(包括栈、全局变量)
    • 恢复世界(Start The World),GC线程进入并发标记阶段
      • 从根对象开始标记,遍历所有可达对象
      • 扫描灰色对象(已发现但未完全扫描的对象)并进行扫描,将其置黑,并将其引用的对象入队为灰色
      • 混合写屏障(Hybrid Write Barrier)确保一致性
    • 完成标记
  3. Mark Termination(标记终止)

    • STW,切换GC状态
      • gcphase从_GCmark切换到_GCmarktermination
      • 停止并发标记任务
      • 执行终结器finalizer,如果有的话
      • 清理mcache以确保没有悬挂对象
  4. Sweep(清理)

    • 切换GC状态
      • gcphase从_GCmarktermination切换回_GCoff
      • 关闭写屏障(Mutator不再协助GC标记)
    • 恢复世界(Start The World),进入并发清扫阶段
      • 清理未被标记的对象
      • 回收mspan到mheap或mcentral,部份回收到mcache
      • Mutator分配内存时,可能会触发增量清扫(Incremental Sweeping),加快回收过程
  5. 满足触发条件,启动下一轮GC

简单的说,标记然后清扫

数据结构

如果没有特别说明的话,下面的耗时字段都指一个GC周期内的

workType

workType类似GMP里的全局调度器schedt,负责GC的任务调度与阶段状态,包括管理GC周期中的各类任务、调度器状态、根对象索引、全局任务队列、并发控制等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// 世界停止前获取,恢复世界后释放。确保同一时间只有一个GC流程在运行
var gcsema uint32 = 1
// 世界停止前获取,恢复世界后释放。确保同一时间只能有一个线程执行STW(其他组件也能STW)
var worldsema uint32 = 1
// workType.startSema // 第1阶段启动前获取并在末尾释放

var work workType

//
type workType struct {
full lfstack // 全局任务缓冲区,wbuf中元素数量不为0
_ cpu.CacheLinePad // 防止false-sharing
empty lfstack // 全局任务缓冲区,wbuf中元素数量为0
_ cpu.CacheLinePad

// mspan,用于分配、存储wbuf-任务缓冲区
// mspan会一次性创建n个mspan,一个返回,剩余n-1个放到empty
wbufSpans struct {
lock mutex
free mSpanList // 空的mspan,一般是busy清空后放到这里
busy mSpanList // 含有wbuf的mspan
}
_ uint32

bytesMarked uint64 // 已标记字节数
markrootNext uint32 // markroot任务id/计数器
markrootJobs uint32 // markroot任务总数量

// nproc是固定值,跟nwait是一对,用于表示标记工作线程数量(Assist-协助线程也会调整这个计数器)
nproc uint32 // 初始值为2^32-1
tstart int64 //
nwait uint32 // 初始值为2^32-1

// 根对象块数量,如nStackRoots == len(stackRoots)
nDataRoots, nBSSRoots, nSpanRoots, nStackRoots int
// 根对象块的基地址(索引),baseEnd是边界
baseData, baseBSS, baseSpans, baseStacks, baseEnd uint32

stackRoots []*g // allgs快照(在标记之前获取)
startSema uint32 // 信号量,GC启动前获取,
markDoneSema uint32 // 信号量,gcMarkDone时使用
bgMarkDone uint32 // 这个字段整个源代码都没有使用

// 0-并发标记清扫 1-STW标记+并发清扫 2-STW标记+STW清扫
mode gcMode // 模式,默认0-gcBackgroundMode,debug时可设置其他模式

userForced bool // 是否用户手动/强制运行
initialHeapLive uint64 // heapLive快照
assistQueue struct { // bgScanCredit额度不足时将g挂起
lock mutex
q gQueue
}

sweepWaiters struct { // 当前g挂起等待第n个GC周期结束(手动触发使用)
lock mutex
list gList // 执行runtime.GC的g,_GCmarktermination时唤醒
}

strongFromWeak struct { // 控制weak->strong指针转换
block bool // 是否阻止weak->strong转换
lock mutex
q gQueue // block为true时将g放到q,false时将q内全部g放进p本地/全局队列
}

cycles atomic.Uint32 // 周期计数器,gcStart时递增

// stwprocs=max(gomaxprocs,ncpu), maxprocs=gomaxprocs
stwprocs, maxprocs int32 // 同p数量
// tSweepTerm - 第1阶段开始时刻
// tMark - 第2阶段开始时刻
// tMarkTerm - 第3阶段开始时刻
// tEnd - 第4阶段开始时刻
tSweepTerm, tMark, tMarkTerm, tEnd int64 // 当前时刻
pauseNS int64 // 当前GC循环累计STW耗时
// heap0 => heapLive快照-heap存活字节数
// heap1 => heapLive快照-heap存活字节数
// heap2 => bytesMarked快照-已标记字节数
heap0, heap1, heap2 uint64 // 当前循环的debug.gctrace heap大小
// GC暂停耗时、GC流程总耗时
cpuStats // CPU统计信息
}

gcControllerState

gcControllerState-垃圾回收节奏控制(pacing),保持系统平稳运行。负责内存使用与触发GC的策略控制,包括控制GC何时触发,计算下一次触发时机、控制GC比例(目标)、动态调整参数(如GOGC)等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
var gcController gcControllerState

// 类似schedt结构体
type gcControllerState struct {
gcPercent atomic.Int32 // 从GOGC环境变量获取,默认100
memoryLimit atomic.Int64 // 从GOMEMLIMIT环境变量获取,默认2^64-1

heapMinimum uint64 // heap内存空间大小,默认为4MB

// 跑道,飞机起飞前需要滑行的距离
// 控制goroutine是否需要协助标记,避免GC跑不完。默认情况下为根对象大小的3倍
runway atomic.Uint64

// 当前并发标记进度
// consMark > 1.0 => GC当前落后于内存分配速度,会启用更激进的GC策略
// consMark < 1.0 => GC当前标记进度是健康的
consMark float64

lastConsMark [4]float64 // 历史4次GC的并发标记进度
gcPercentHeapGoal atomic.Uint64 // 目标heap大小
sweepDistMinTrigger atomic.Uint64 // GC启动的一个底线阈值,非清扫阶段为0,否则为heapLive+1MB
triggered uint64 // 默认2^64-1,GC启动时为heapLive快照
lastHeapGoal uint64 // GC结束时计算的目标heap大小
heapLive atomic.Uint64 // heap存活字节数

// 标记终止阶段同步heapScanWork,与lastHeapScan不同,内存分配时也会继续累计
heapScan atomic.Uint64 // heapScanWork快照
lastHeapScan uint64 // heapScanWork快照
lastStackScan atomic.Uint64 // stackScanWork快照
maxStackScan atomic.Uint64 // 累计所有的栈字节数
globalsScan atomic.Uint64 // 全部模块的bss+data段大小

heapMarked uint64 // 上一次GC后heap存活字节数,heapLive快照

heapScanWork atomic.Int64 // heap扫描字节数,同gcw.heapScanWork
stackScanWork atomic.Int64 // stack扫描字节数
globalsScanWork atomic.Int64 // bss/data扫描字节数

bgScanCredit atomic.Int64 // 扫描额度,同gcw.heapScanWork
assistTime atomic.Int64 // GC助攻耗时

dedicatedMarkTime atomic.Int64 // 累计所有p的标记耗时(专用标记任务)
fractionalMarkTime atomic.Int64 // 累计所有p的标记耗时(比例标记任务)
idleMarkTime atomic.Int64 // 累计所有p的标记耗时(空闲标记任务)

markStartTime int64 // 当前循环开始时刻,gcStart时纪录

dedicatedMarkWorkersNeeded atomic.Int64 // 专用标记任务需要的线程数量
idleMarkWorkers atomic.Uint64 // 空闲标记线程数量,一般是procs-专用标记线程数量
// 下面两个互为倒数
assistWorkPerByte atomic.Float64 // 每分配1字节需要辅助完成多少GC工作量
assistBytesPerWork atomic.Float64 // 每完成1单位GC工作量可以分配多少字节
fractionalUtilizationGoal float64 // 比例标记任务利用率目标,为0时不需要标记线程

heapInUse sysMemStat // heap内存使用量(mSpanInUse)
heapReleased sysMemStat // heap内存释放量(释放回OS)
// heapFree在allocSpan时减少,freeSpanLocked时增加,一般为负数
heapFree sysMemStat // heap内存可复用量

totalAlloc atomic.Uint64 // 累计已分配字节数
totalFree atomic.Uint64 // 累计回收对象数量
// 已映射且可用的内存量(总内存),最底层OS级别的内存统计
// sysAlloc、sysUsed时增加,sysUnused、sysFree、sysFault时减少
mappedReady atomic.Uint64

test bool // 测试,忽略
_ cpu.CacheLinePad
}

gcCPULimiterState

CPU限制器,负责限制GC对CPU的占用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var gcCPULimiter gcCPULimiterState

// 只会在gcStart和gcMarkDone调整
type gcCPULimiterState struct {
lock atomic.Uint32 // 0-未锁定,1-已锁定
enabled atomic.Bool // 是否已经触发限速状态,fill==capacity时限制GC运行
gcEnabled bool // gcBlackenEnabled快照
transitioning bool // 是否处于过渡状态,true-意味着GC目前正在调整CPU限制
test bool // 测试,忽略

bucket struct { // 水桶
// capacity是整个水桶的容量,=nprocs*1e9 => 每个CPU有1s
// fill是水桶剩余量,可以理解为GC额度,初始值为capacity,意味着从一开始就限制GC运行
fill, capacity uint64 // fill <= capacity
}

overflow uint64 // 累计bucket溢出部份(从程序开始一直累计)
assistTimePool atomic.Int64 // 从上一次更新开始累计的助攻积分
idleMarkTimePool atomic.Int64 //
idleTimePool atomic.Int64 // 从上一次更新开始累计的p空闲时辅助标记的耗时
lastUpdate atomic.Int64 // 上一次更新的时刻
lastEnabledCycle atomic.Uint32 // enabled为true时,纪录为numgc+1。numgc => gc计数器,类似cycle
nprocs int32 //
}

清扫器

sweeper-清扫器,负责将内存回收到mheap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var sweep sweepdata

// sweeper状态,负责清理不可达对象的内存
type sweepdata struct {
lock mutex
// 下面两个是sweeper专用的
g *g // sweeper
parked bool // 挂起时为true,被唤醒后重置为false

// 其他g可以协助sweeper并发清扫
// 为什么还有计数器呢?因为除了第一个默认的sweeper,其他的g可以协助并发清扫
active activeSweep // 计数器,最高位为1时表示清扫完毕
centralIndex sweepClass // 索引,指向当前未清扫的mspan,用于mcentral
}

type activeSweep struct {
// 最高位是flag,为1时表示队列为空,剩余31位为计数器
state atomic.Uint32
}

type sweepClass uint32

内存回收器

内存回收器负责将内存归还OS,其中

  1. scavenge-负责回收目标计算、回收时机判断
  2. scavenger-节奏调度器
    • 当前目标释放速率(sleepRatio, targetCPUFraction)
    • 实际是否要执行(shouldStop)
    • 具体怎么执行(scavenge回调函数由pageAlloc注入)
    • 用PI控制器sleepController平滑地控制回收速率:更类似GC的pacer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 回收目标计算、回收时机判断
var scavenge struct {
// 1. lastHeapGoal为0
// 2. heapInUse+heapFree < gcPercentGoal
// 3. heapInUse+heapFree < gcPercentGoal+physPageSize
// 以上三种情况设置为2^64-1,否则需要计算得出
gcPercentGoal atomic.Uint64 // GC触发临界点

// 如果超过95%的memoryLimit则设置为95%的memoryLimit,否则设置为2^64-1
memoryLimitGoal atomic.Uint64 // 内存限制

assistTime atomic.Int64 // 助攻耗时
backgroundTime atomic.Int64 // 运行耗时
}

var scavenger scavengerState

// pacing-节奏调度器
type scavengerState struct {
lock mutex

g *g // scavenger
timer *timer // 定时器,看起来没什么用
sysmonWake atomic.Uint32 // 为1时sysmon将会唤醒scavenger
parked bool // 是否挂起
printControllerReset bool //
targetCPUFraction float64 //
sleepRatio float64 // 低值意味着更多睡眠,高值意味着更激进的scavenging
sleepController piController //
controllerCooldown int64 //
sleepStub func(n int64) int64 //
scavenge func(n uintptr) (uintptr, int64) // pageAlloc执行scavenge,累计耗时到backgroundTime
shouldStop func() bool // 只要没达到阈值,scavenger就不需要回收

gomaxprocs func() int32 // 测试用
}

// pageAlloc相关
// 回收索引-负责追踪哪些内存物理页已经被回收或可被回收
type scavengeIndex struct {
// 用于追踪chunk中哪些页已经被释放
// 总大小512MB,可通过load方法转换为scavChunkData
chunks []atomicScavChunkData //
min, max atomic.Uintptr // 最小base、最大limit
minHeapIdx atomic.Uintptr // 全局最小的chunkIndex
// atomicOffAddr中,负数的意思是marked,表示有可回收的页
searchAddrBg atomicOffAddr // 搜索地址,由后台运行的scavenger更新
searchAddrForce atomicOffAddr // 搜索地址,由分配内存时协助回收更新
freeHWM offAddr // free函数执行时纪录的最高地址
gen uint32 // 版本计数器
test bool // 测试用
}

// 8字节,可转换为scavChunkData
type atomicScavChunkData struct {
// | 32bit | 6bit | 10bit | 16bit |
// | gen | scavChunkFlags | lastInUse | inUse |
value atomic.Uint64
}

// 12个字节
type scavChunkData struct {
inUse uint16 // 页数,表示有多少个页当前已分配,只有低10位被使用
lastInUse uint16 // inUse快照,版本变更时纪录
gen uint32 // 版本计数器
scavChunkFlags // 只有低6位在使用
}

type scavChunkFlags uint8

缓冲区

这几个缓冲区都放在P内部,作为本地缓冲区,全局缓冲区放在workType

  1. workbuf-任务缓冲区
  2. wbBuf-写屏障缓冲区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 工作队列
type gcWork struct {
wbuf1, wbuf2 *workbuf // 任务缓冲区
bytesMarked uint64 //
heapScanWork int64 // 扫描字节数
flushedWork bool //
}

// 任务缓冲区,固定2KB
type workbuf struct {
_ sys.NotInHeap
//
workbufhdr
// 253个指针
obj [(_WorkbufSize - unsafe.Sizeof(workbufhdr{})) / goarch.PtrSize]uintptr
}

// 24字节
type workbufhdr struct {
node lfnode //
nobj int // 对象数量
}

// 写屏障缓冲,4112字节
type wbBuf struct {
next uintptr // 指针的地址,指向下一个元素时移动8个字节(指针大小)
end uintptr // 边界,指向最后一个元素的末尾
// 不直接使用buf
buf [wbBufEntries]uintptr // 512个指针
}

其他

  1. gcTrigger-触发类型
  2. gcBgMarkWorkerNode-标记工作线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 触发类型
type gcTrigger struct {
kind gcTriggerKind // 类型
now int64 // 当前时刻,时间触发才有
n uint32 // 版本计数器,手动触发才有,从work.cycles同步
}

// 标记工作线程,这是单个节点,所有的节点会存储在全局变量gcBgMarkWorkerPool中
type gcBgMarkWorkerNode struct {
node lfnode // 链表,将当前节点跟其他节点链接起来
gp guintptr // g
m muintptr // m,禁止抢占
}

// 无锁栈
type lfnode struct {
next uint64 // next指针
pushcnt uintptr // 计数器/索引
}

触发方式

时间触发

时间触发的GC注册在init函数,由runtime.main负责启动,如果检查到2min内没有执行过任何GC,则触发运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// src/runtime/proc.go
// start forcegc helper goroutine
func init() {
// gcTriggerTime类型的GC
go forcegchelper()
}

// 由sysmon管理
type forcegcstate struct {
lock mutex // 锁
g *g // g
idle atomic.Bool // 空闲时挂起休眠,g运行时设置为true
}

var forcegc forcegcstate

// gcTriggerTime类型的GC
func forcegchelper() {
// 纪录当前g
forcegc.g = getg()
// 锁初始化
lockInit(&forcegc.lock, lockRankForcegc)

for {
// 加锁
lock(&forcegc.lock)
// 期望idle为false
if forcegc.idle.Load() {
throw("forcegc: phase error")
}

// idle设置为true
forcegc.idle.Store(true)

// 当前g让出CPU,g0执行调度运行其他g
// 在内部g、m解除绑定后会解锁forcegc.lock
goparkunlock(&forcegc.lock, waitReasonForceGCIdle, traceBlockSystemGoroutine, 1)
// 被sysmon唤醒

// 默认为0,忽略
if debug.gctrace > 0 {
println("GC forced")
}

// gcTriggerTime类型,超过2min未GC则强制运行
gcStart(gcTrigger{kind: gcTriggerTime, now: nanotime()})
}
}

堆大小触发

每次分配内存时,判断heap内存大小是否达到临界点,达到临界点则触发运行

1
2
3
if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
gcStart(t)
}

手动触发

由用户调用runtime.GC()手动触发,大概的逻辑为:

  1. 等待当前周期结束
  2. 执行新周期的GC
  3. 等待标记、清扫完全结束
  4. 更新heap统计数据

该操作会阻塞当前线程,甚至是阻塞整个程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
func GC() {
n := work.cycles.Load()
// 等待第n个周期结束(_GCmarktermination时唤醒)
gcWaitOnMark(n)

// 执行GC
gcStart(gcTrigger{kind: gcTriggerCycle, n: n + 1})

// 等待第n+1个周期结束(_GCmarktermination时唤醒)
gcWaitOnMark(n + 1)

// runtime.GC()被很多测试、压测调用,需要确保GC完成sweep阶段

// 当前周期 and 完成剩余mspan的清扫
for work.cycles.Load() == n+1 && sweepone() != ^uintptr(0) {
// 清理了一个mspan
// 同协程yield关键字,当前G让出CPU,g0执行调度运行其他g,非抢占
Gosched()
// 被重新调度
}

// heap的状态可能还在周期n

// 当前周期 and sweep阶段还没结束
for work.cycles.Load() == n+1 && !isSweepDone() {
// 同协程yield关键字,当前G让出CPU,g0执行调度运行其他g,非抢占
Gosched()
// 被重新调度
}

// 到这里,sweep阶段确定是结束了

mp := acquirem()
cycle := work.cycles.Load()
// 当前周期 or 下一个周期
if cycle == n+1 || (gcphase == _GCmark && cycle == n+2) {
// 将的heap统计数据累加到active
mProf_PostSweep()
}
releasem(mp)
}

完整流程

第1阶段:Sweep Termination(清理终止)

大概流程如下

  1. guard,检查函数运行的前提条件
  2. 协助sweeper清理剩余的mspan
  3. 创建标记工作线程(这个阶段前,将世界停止)
  4. 重置GC状态
  5. 停止所有p
  6. 清扫、回收
  7. 开始启动GC(设置各种状态、纪录快照等,开启写屏障)
  8. 一切都准备好了,可以并发标记了(这个阶段后,恢复世界)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
func gcStart(trigger gcTrigger) {
// 1. guard

mp := acquirem()
// g0或m禁止抢占
if gp := getg(); gp == mp.g0 || mp.locks > 1 || mp.preemptoff != "" {
// 不处理
releasem(mp)
return
}
releasem(mp)
mp = nil

// 测试,忽略
if gp := getg(); gp.syncGroup != nil {
sg := gp.syncGroup
gp.syncGroup = nil
defer func() {
gp.syncGroup = sg
}()
}

// 2. 协助清扫

// 满足GC运行条件则把剩余的mspan都清扫完
for trigger.test() && sweepone() != ^uintptr(0) {
}

// 不满足GC运行条件 or 没有mspan需要清扫

// 尝试获取work.startSema,失败则挂起等待
semacquire(&work.startSema)
// 被唤醒

// double-check
// 不满足GC运行条件
if !trigger.test() {
// 恢复世界后释放,唤醒其他g,可以运行其他GC了,虽然可能会被阻挡
semrelease(&work.startSema)
return
}

// 没有mspan需要清扫,可以启动新流程

// 默认,并发GC和sweep
mode := gcBackgroundMode
// gcstoptheworld默认为0,下面两个条件不管
if debug.gcstoptheworld == 1 {
mode = gcForceMode
} else if debug.gcstoptheworld == 2 {
mode = gcForceBlockMode
}

// 尝试获取gcsema,失败则挂起等待(确保同一时间只有一个GC流程在运行)
semacquire(&gcsema)
// 尝试获取worldsema,失败则挂起等待(确保同一时间只能有一个线程执行STW)
semacquire(&worldsema)

// gcTriggerCycle => 手动强制执行(这里用于统计)
work.userForced = trigger.kind == gcTriggerCycle

// 遍历allp
for _, p := range allp {
// 一般情况下flushGen与sweepgen是同步的
if fg := p.mcache.flushGen.Load(); fg != mheap_.sweepgen {
println("runtime: p", p.id, "flushGen", fg, "!= sweepgen", mheap_.sweepgen)
throw("p mcache not flushed")
}
}

// 3. 创建标记工作线程

// 每个p启动一个标记工作线程gcBgMarkWorker,负责第2阶段,先挂起等待调度器调度
// (调度的前提是gcBlackenEnabled=1,下方设置)
gcBgMarkStartWorkers()

// 4. 重置GC状态

// 汇编,切换到g0运行gcResetMarkState
// 所有g标记为栈未扫描、助攻积分清0,所有heapArena清除pageMarks,重置bytesMarked、同步heapLive
systemstack(gcResetMarkState)

// 5. 停止所有p

// 同p的总量
work.stwprocs, work.maxprocs = gomaxprocs, gomaxprocs
// =max(gomaxprocs,ncpu)
if work.stwprocs > ncpu {
// 取实际CPU核心数量
work.stwprocs = ncpu
}
// heap存活字节数
work.heap0 = gcController.heapLive.Load()
// STW耗时
work.pauseNS = 0
// gcBackgroundMode,默认,并发标记、清扫
work.mode = mode

// 当前时刻
now := nanotime()
// 第1阶段开始时刻
work.tSweepTerm = now

// STW原因、耗时信息
var stw worldStop
// 切换到g0执行
systemstack(func() {
// 将所有p都停止,返回原因、时间信息
stw = stopTheWorldWithSema(stwGCSweepTerm)
})

// 到这里,所有的p都放进空闲队列了

// 6. 清扫、回收

// CPU暂停时间GCPauseTime、GCTotalTime更新 => +=stoppingCPUTime*1
work.cpuStats.accumulateGCPauseTime(stw.stoppingCPUTime, 1)

// 切换到g0执行
systemstack(func() {
// 清理所有mspan、重置mcentral、唤醒scavenger、gcBitsArenas迭代
finishsweep_m()
})

// GC第1阶段时,清理sync.Pool、sudog缓存、defer pool及其他
clearpools()

// 7. 开始启动GC

// 周期计数器n+1
work.cycles.Add(1)

// 重置相关字段,计算不同模式的标记工作线程目标,设置最大标记工作线程数,计算辅助GC的工作量转换参数
gcController.startCycle(now, int(gomaxprocs), trigger)

// 同步gcEnabled、标记当前处于过渡状态,计算mutator耗时跟gc耗时,判断是否需要限制GC运行(加锁)
gcCPULimiter.startGCTransition(true, now)

// 非默认模式,忽略
if mode != gcBackgroundMode {
// 同步标记,如果允许user类型g运行则把阻塞的g全部放回全局队列并尝试获取p绑定m唤醒运行
schedEnableUser(false)
}

// 同步gcphase,如果是_GCmark或_GCmarktermination,开启写屏障
setGCPhase(_GCmark)

// nproc、nwait设为32位无符号数最大值(需要在协助线程启动前运行)
gcBgMarkPrepare()
// 获取快照,计算各个根对象的块数量、基地址(索引)
gcMarkRootPrepare()

// tiny区域对象置灰放入队列
gcMarkTinyAllocs()

// gcBlackenEnabled意味着写屏障、协助线程已开启,可以开始标记
atomic.Store(&gcBlackenEnabled, 1)
// 到这里,第2阶段已经可以准备运行了 => gcBgMarkWorker可以开始标记了

// 禁止抢占
mp = acquirem()

// CPU暂停时间GCPauseTime、GCTotalTime更新 => +=d*maxprocs
work.cpuStats.accumulateGCPauseTime(nanotime()-stw.finishedStopping, work.maxprocs)

// 8. 一切都准备好了,可以并发标记了

// 并发标记
systemstack(func() {
// epoll轮询、p数量调整、唤醒sysmon、唤醒m绑定p执行任务、统计等
now = startTheWorldWithSema(0, stw)
// 到这里,已经有p和m被唤醒绑定并去寻找g运行了,第2阶段确实在运行了

// STW耗时
work.pauseNS += now - stw.startedStopping
// 第2阶段开始时刻
work.tMark = now

// 解除过渡状态,计算mutator耗时跟gc耗时,判断是否需要限制GC运行(加锁)
gcCPULimiter.finishGCTransition(now)
})

// 恢复世界后释放,唤醒其他g,其他组件可以开始STW
// 注意:需要在Gosched前先释放,后面会重新获取,否则造成死锁
semrelease(&worldsema)
releasem(mp)

// 非默认模式,忽略
if mode != gcBackgroundMode {
// 同协程yield关键字,当前G让出CPU,g0执行调度运行其他g,非抢占
Gosched()
}

// 恢复世界后释放,唤醒其他g,可以运行其他GC了,虽然可能会被阻挡
semrelease(&work.startSema)
}

// 每个p启动一个标记工作线程gcBgMarkWorker,负责第2阶段,先挂起等待调度器调度
func gcBgMarkStartWorkers() {
// 数量足够
if gcBgMarkWorkerCount >= gomaxprocs {
return
}

// 禁止抢占
mp := acquirem()
// 带缓存channel
ready := make(chan struct{}, 1)
// 恢复
releasem(mp)

// 数量不足
for gcBgMarkWorkerCount < gomaxprocs {
// 禁止抢占
mp := acquirem()
// 启动gcBgMarkWorker
go gcBgMarkWorker(ready)
// 恢复
releasem(mp)

// 等待gcBgMarkWorker准备好
<-ready

// 计数器更新
gcBgMarkWorkerCount++
}
}

第2阶段:Mark(标记)

大概流程如下

  1. 挂起等待调度器唤醒
  2. 根据模式设置标志位,运行标记任务
    1. 初始化参数
    2. 扫描根对象
    3. 从队列获取灰色对象标记
  3. 重置相关字段、纪录耗时,如果是最后一个标记工作线程,启动第3阶段:Mark Termination(标记终止)
    1. STW,确定已经没有标记任务
    2. 根据统计数据调整栈的初始大小
    3. 唤醒所有因为辅助标记、weak->strong转换挂起的g
    4. 运行user类型的g运行
    5. 计算目标heap大小和并发标记进度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
// 标记工作线程,负责第2阶段:Mark(标记)
func gcBgMarkWorker(ready chan struct{}) {
gp := getg()

// 禁止抢占
gp.m.preemptoff = "GC worker init"
// worker
node := new(gcBgMarkWorkerNode)
// 重置(可抢占)
gp.m.preemptoff = ""

// 暂存g
node.gp.set(gp)
// 暂存m
node.m.set(acquirem())

// 通知父g
ready <- struct{}{}

// 后台mark线程由gcController.findRunnableGCWorker调度

for {
// 当前G让出CPU,g0执行调度运行其他g
gopark(func(g *g, nodep unsafe.Pointer) bool {
// 挂起前运行
// 创建worker放到gcBgMarkWorkerPool

// 类型转换
node := (*gcBgMarkWorkerNode)(nodep)

// g与m已关联
if mp := node.m.ptr(); mp != nil {
// 释放
releasem(mp)
}

// 把g放到gcBgMarkWorkerPool队列头部
gcBgMarkWorkerPool.push(&node.node)
return true
}, unsafe.Pointer(node), waitReasonGCWorkerIdle, traceBlockSystemGoroutine, 0)
// 被调度器唤醒

// 禁止抢占
node.m.set(acquirem())
// m.p不会被改变
pp := gp.m.p.ptr()

// GC未启动/停止,异常
if gcBlackenEnabled == 0 {
println("worker mode", pp.gcMarkWorkerMode)
throw("gcBgMarkWorker: blackening not enabled")
}

// mode为默认值 => 未更新
if pp.gcMarkWorkerMode == gcMarkWorkerNotWorker {
throw("gcBgMarkWorker: mode not set")
}

// GC标记线程的执行开始时刻
startTime := nanotime()
pp.gcMarkWorkerStartTime = startTime
var trackLimiterEvent bool
// 空闲标记任务
if pp.gcMarkWorkerMode == gcMarkWorkerIdleMode {
// stamp存储limiterEventIdleMarkWork和startTime
trackLimiterEvent = pp.limiterEvent.start(limiterEventIdleMarkWork, startTime)
}

// 队列容量/计数器 nwait-=1
decnwait := atomic.Xadd(&work.nwait, -1)
// 异常,nproc>=nwait
if decnwait == work.nproc {
println("runtime: work.nwait=", decnwait, "work.nproc=", work.nproc)
throw("work.nwait was > work.nproc")
}

// 下面扫描根对象、灰色对象队列并标记(标记g为可抢占,使得栈可被扫描)
systemstack(func() {
// 改为_Gwaiting状态并设置waitreason,可抢占
casGToWaitingForGC(gp, _Grunning, waitReasonGCWorkerActive)

// gcMarkWorkerMode在findRunnableGCWorker设置
switch pp.gcMarkWorkerMode {
default: // 为0或其他,异常状态
throw("gcBgMarkWorker: unexpected gcMarkWorkerMode")
case gcMarkWorkerDedicatedMode: // 专用标记任务,p被mark线程占用,不可被抢占
// 设置gcDrainFlushBgCredit|gcDrainUntilPreempt标记,执行gcDrain(可被抢占)
gcDrainMarkWorkerDedicated(&pp.gcw, true)
// 被抢占中
if gp.preempt {
// 抽走p本地队列所有数据
if drainQ, n := runqdrain(pp); n > 0 {
// 全局队列加锁
lock(&sched.lock)
// 把一批g放到全局队列
globrunqputbatch(&drainQ, int32(n))
// 解锁
unlock(&sched.lock)
}
}
// 设置gcDrainFlushBgCredit标记,执行gcDrain(不可被抢占)
gcDrainMarkWorkerDedicated(&pp.gcw, false)
case gcMarkWorkerFractionalMode: // 比例标记任务,可被抢占
// 设置gcDrainFractional|gcDrainUntilPreempt|gcDrainFlushBgCredit标记,执行gcDrain
// pollFractionalWorkerExit返回true时会自行抢占
gcDrainMarkWorkerFractional(&pp.gcw)
case gcMarkWorkerIdleMode: // 空闲标记任务
// 设置gcDrainIdle|gcDrainUntilPreempt|gcDrainFlushBgCredit标记,执行gcDrain
// 如果p有其他g,转而执行其他g
gcDrainMarkWorkerIdle(&pp.gcw)
}
// 非_Gscan状态转换,统计g位于_Grunnable、_Gwaiting状态时所耗费的时间
casgstatus(gp, _Gwaiting, _Grunning)
})

// 耗时
now := nanotime()
duration := now - startTime
// 累计标记耗时、复原计数器
gcController.markWorkerStop(pp.gcMarkWorkerMode, duration)

if trackLimiterEvent {
// 重置stamp字段,纪录耗时
pp.limiterEvent.stop(limiterEventIdleMarkWork, now)
}
// 比例标记任务
if pp.gcMarkWorkerMode == gcMarkWorkerFractionalMode {
// 累计比例标记任务任务下的标记耗时
atomic.Xaddint64(&pp.gcFractionalMarkTime, duration)
}

// 计数器,同时也是用于判断是否是最后一个标记工作线程
incnwait := atomic.Xadd(&work.nwait, +1)
// 异常
if incnwait > work.nproc {
println("runtime: p.gcMarkWorkerMode=", pp.gcMarkWorkerMode,
"work.nwait=", incnwait, "work.nproc=", work.nproc)
throw("work.nwait > work.nproc")
}

// 重置为0
pp.gcMarkWorkerMode = gcMarkWorkerNotWorker

// 队列为空(最后一个标记工作线程) and 是否还有标记任务(根对象是否扫描完,任务缓冲区是否为空)
if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {
releasem(node.m.ptr())
// 解除m绑定
node.m.set(nil)

//
gcMarkDone()
}
}
}

// 空闲标记任务
func gcDrainMarkWorkerIdle(gcw *gcWork) {
gcDrain(gcw, gcDrainIdle|gcDrainUntilPreempt|gcDrainFlushBgCredit)
}

// 专用标记任务
func gcDrainMarkWorkerDedicated(gcw *gcWork, untilPreempt bool) {
flags := gcDrainFlushBgCredit
if untilPreempt {
// 可被抢占
flags |= gcDrainUntilPreempt
}
gcDrain(gcw, flags)
}

// 比例标记任务
func gcDrainMarkWorkerFractional(gcw *gcWork) {
gcDrain(gcw, gcDrainFractional|gcDrainUntilPreempt|gcDrainFlushBgCredit)
}

// 扫描根对象、灰色对象队列并标记
func gcDrain(gcw *gcWork, flags gcDrainFlags) {
// 写屏障未开启
if !writeBarrier.enabled {
throw("gcDrain phase incorrect")
}

// g
gp := getg().m.curg
// p (前提是非抢占)
pp := gp.m.p.ptr()
// 一般都有设置gcDrainUntilPreempt
preemptible := flags&gcDrainUntilPreempt != 0
// 默认开启
flushBgCredit := flags&gcDrainFlushBgCredit != 0
// p空闲
idle := flags&gcDrainIdle != 0

// 快照
initScanWork := gcw.heapScanWork

// 初始为2^64-1,后面根据任务类型调整,空闲标记任务和比例标记任务只有100000额度
checkWork := int64(1<<63 - 1)

// 只有空闲标记任务和比例标记任务才有
var check func() bool

// 空闲标记任务 or 比例标记任务
if flags&(gcDrainIdle|gcDrainFractional) != 0 {
// heapScanWork快照+100000
checkWork = initScanWork + drainCheckThreshold
if idle {
// 空闲标记任务
// 队列为空时执行netpoll轮询,检查有g返回true
check = pollWork
} else if flags&gcDrainFractional != 0 {
// 比例标记任务
// 判断标记工作线程是否需要自我抢占,超过目标值返回true
check = pollFractionalWorkerExit
}
}

// 扫描根对象
if work.markrootNext < work.markrootJobs {
// 如果可抢占、有g想要STW、有g在运行forEachP,停止
for !(gp.preempt && (preemptible || sched.gcwaiting.Load() || pp.runSafePointFn != 0)) {
// job=markrootNext; markrootNext+=1
job := atomic.Xadd(&work.markrootNext, +1) - 1
// 索引/计数器超限,没任务了
if job >= work.markrootJobs {
// 退出
break
}
// 根据索引扫描指定的根对象,扫描的字节数除了返回还会累计到gcController
markroot(gcw, job, flushBgCredit)
// 空闲标记任务和比例标记任务需要运行check函数
if check != nil && check() {
// 需要让出CPU,退出标记任务
goto done
}
}
}

// 扫描灰色对象队列
// 如果可抢占、有g想要STW、有g在运行forEachP,停止
for !(gp.preempt && (preemptible || sched.gcwaiting.Load() || pp.runSafePointFn != 0)) {
// work.full为空 => 确保work.full不为空
if work.full == 0 {
// wbuf2不为空则全部放入work.full,否则将wbuf1的一半放到work.full
gcw.balance()
}

// 先从工作队列拿数据,没有就从写屏障拿

// 从wbuf获取一个obj,未初始化或为空直接返回
b := gcw.tryGetFast()
// 没有拿到
if b == 0 {
// 从wbuf获取一个obj
b = gcw.tryGet()
// 还是没有
if b == 0 {
// 将符合条件的写屏障wbBuf数据放到wbuf,清空wbBuf
wbBufFlush()
// 从wbuf获取一个obj
b = gcw.tryGet()
}
}
// 3次均失败
if b == 0 {
// 退出
break
}

// 从本地任务缓冲区获取到对象

// 扫描一个对象(最多128KB,剩余放到任务缓冲区)内所有指针,设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
scanobject(b, gcw)

// >= 2000 => heapScanWork每达到2000则刷新到全局计数器里
if gcw.heapScanWork >= gcCreditSlack {
// 累计到全局heapScanWork
gcController.heapScanWork.Add(gcw.heapScanWork)
// 默认开启
if flushBgCredit {
// 根据额度唤醒一批g,额度有剩余就累计到全局额度bgScanCredit(与gcParkAssist成对使用)
gcFlushBgCredit(gcw.heapScanWork - initScanWork)
// 快照清0,跟heapScanWork同步
initScanWork = 0
}

// 额度扣除
checkWork -= gcw.heapScanWork
//重置
gcw.heapScanWork = 0

// 额度不足
if checkWork <= 0 {
// 说明是空闲标记任务和比例标记任务
// 额度再加100000
checkWork += drainCheckThreshold
// 空闲标记任务和比例标记任务需要运行check函数
if check != nil && check() {
// 需要让出CPU,退出标记任务
break
}
}
}
}

done:
// 收尾,循环后,本地heapScanWork有剩余,刷新到全局计数器里(看循环内部代码即可)
if gcw.heapScanWork > 0 {
// 累计到全局heapScanWork
gcController.heapScanWork.Add(gcw.heapScanWork)
// 默认开启
if flushBgCredit {
// 根据额度唤醒一批g,额度有剩余就累计到全局额度bgScanCredit(与gcParkAssist成对使用)
gcFlushBgCredit(gcw.heapScanWork - initScanWork)
}
// 重置
gcw.heapScanWork = 0
}
}

// 负责启动第3阶段:Mark Termination(标记终止)
func gcMarkDone() {
// 尝试获取work.markDoneSema,失败则挂起等待(确保只有一个线程在执行)
semacquire(&work.markDoneSema)

top:
// !(标记阶段 and 队列为空 and 是否还有标记任务(根对象是否扫描完,任务缓冲区是否为空))
if !(gcphase == _GCmark && work.nwait == work.nproc && !gcMarkWorkAvailable(nil)) {
// 还在有对象需要标记

// 释放信号量,唤醒其他g
semrelease(&work.markDoneSema)
return
}

// 尝试获取worldsema,失败则挂起等待(STW)
semacquire(&worldsema)

// 禁止weak->strong转换
work.strongFromWeak.block = true

// 清空所有本地缓存并收集标记
gcMarkDoneFlushed = 0

// 让p的g进入等待,强制让p进入空闲状态,执行safePoint函数
forEachP(waitReasonGCMarkTermination, func(pp *p) {
// 将符合条件的写屏障wbBuf数据放到wbuf,清空wbBuf
wbBufFlush1(pp)

// wbuf1、wbuf2根据容量选择放入work.empty或work.full队列
pp.gcw.dispose()

// 表示还有更多灰色对象需要标记
if pp.gcw.flushedWork {
// 累计
atomic.Xadd(&gcMarkDoneFlushed, 1)
pp.gcw.flushedWork = false
}
})

// 还有更多灰色对象被发现,不能进入第3阶段
if gcMarkDoneFlushed != 0 {
// 恢复世界后释放,唤醒其他g,其他组件可以开始STW
semrelease(&worldsema)
// 重试
goto top
}

// debug,忽略
for gcDebugMarkDone.spinAfterRaggedBarrier.Load() {
}

// 已经没有任何标记任务了,可以开启第3阶段

now := nanotime()
// 第3阶段开始时刻
work.tMarkTerm = now
// 禁止抢占
getg().m.preemptoff = "gcing"

var stw worldStop
systemstack(func() {
// 将所有p都停止,返回原因、时间信息
stw = stopTheWorldWithSema(stwGCMarkTerm)
})

// CPU暂停时间GCPauseTime、GCTotalTime更新 => +=d*maxprocs
work.cpuStats.accumulateGCPauseTime(stw.stoppingCPUTime, 1)

// 因为写屏障的缘故,可能还有一些任务残留
restart := false
systemstack(func() {
for _, p := range allp {
// 将符合条件的写屏障wbBuf数据放到wbuf,清空wbBuf
wbBufFlush1(p)
// wbuf队列不为空,不能进入第3阶段
if !p.gcw.empty() {
restart = true
break
}
}
})
// 回到开头重试
if restart {
gcDebugMarkDone.restartedDueTo27993 = true

// 重置(可抢占)
getg().m.preemptoff = ""
systemstack(func() {
// CPU暂停时间GCPauseTime、GCTotalTime更新 => +=d*maxprocs
work.cpuStats.accumulateGCPauseTime(nanotime()-stw.finishedStopping, work.maxprocs)

// epoll轮询、p数量调整、唤醒sysmon、唤醒m绑定p执行任务、统计等
now := startTheWorldWithSema(0, stw)
// STW耗时
work.pauseNS += now - stw.startedStopping
})
// 恢复世界后释放,唤醒其他g,其他组件可以开始STW
semrelease(&worldsema)
// 重试
goto top
}

// 到这里确定可以进入第3阶段了

// 根据统计数据调整栈的初始大小
gcComputeStartingStackSize()

// GC未启动/停止 => 协助线程、标记工作线程已停止,禁止标记
atomic.Store(&gcBlackenEnabled, 0)

// 同步gcEnabled、标记当前处于过渡状态,计算mutator耗时跟gc耗时,判断是否需要限制GC运行(加锁)
gcCPULimiter.startGCTransition(false, now)

// 唤醒所有被阻塞的g(辅助标记)
gcWakeAllAssists()

// 允许所有weak->strong转换
work.strongFromWeak.block = false
// 将strongFromWeak.q所有的g放进本地/全局队列,并尝试唤醒m处理
gcWakeAllStrongFromWeak()

// 释放信号量,唤醒其他g,使其他g可以调用gcMarkDone
semrelease(&work.markDoneSema)

// 同步标记,如果允许user类型g运行则把阻塞的g全部放回全局队列并尝试获取p绑定m唤醒运行
schedEnableUser(true)

// 从gcStart启动循环,到这里终止循环
// 计算目标heap大小和并发标记进度
gcController.endCycle(now, int(gomaxprocs), work.userForced)

// 进入第3阶段:Mark Termination(标记终止)(该函数会恢复世界-Start The World)
gcMarkTermination(stw)
}

第3阶段:Mark Termination(标记终止)

大概流程如下

  1. 设置各种状态
    • gcphase切换到_GCmarktermination,继续开启写屏障
    • m禁止抢占,g切换到_Gwaiting,可抢占
    • 清空allgs快照、gcw/mcache等残留处理
  2. gcphase切换到_GCoff,关闭写屏障,开启第4阶段:Sweep(清理)
  3. 统计信息并计算pacing参数,重置scavenger状态
    • 同步heap内存使用量
    • 更新memstats、cpuStats耗时信息
    • 设置GC启动阈值、计算目标heap大小和跑道大小
    • 计算辅助GC的工作量转换参数
    • 计算GC触发阈值和目标heap大小
    • 更新sweeper、scavenger的pacing参数
    • 重置scavenge状态、更新scavengeIndex状态
  4. 收尾/清理
    • g切换到_Grunning,m允许抢占
    • 将sweepWaiters放进本地/全局队列,并尝试唤醒m处理
    • epoll轮询、p数量调整、唤醒sysmon、唤醒m绑定p执行任务、统计等
    • 清空empty链表,将busy链表数据搬到free链表
    • stackpool内所有allocCount为0的mspan以及stackLarge所有mspan全部释放
    • mcache清理
      • alloc列表mspan放到partial或full链表、tiny区域清空
      • 清空stackcache,如果是_GCoff阶段,将空的mspan释放回mheap
    • pageCache清空
    • 如果目标heap大小超过1GB,尝试开启大页支持

到这里,世界开始恢复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
func gcMarkTermination(stw worldStop) {
// 同步gcphase,如果是_GCmark或_GCmarktermination,开启写屏障
setGCPhase(_GCmarktermination)

// 同步heapLive快照
work.heap1 = gcController.heapLive.Load()
// 单调时钟
startTime := nanotime()

mp := acquirem()
// 禁止抢占
mp.preemptoff = "gcing"
mp.traceback = 2

curgp := mp.curg
// 改为_Gwaiting状态并设置waitreason,可抢占
casGToWaitingForGC(curgp, _Grunning, waitReasonGarbageCollection)

// 切换到g0执行
systemstack(func() {
// 清空allgs快照、gcw/mcache等残留处理
gcMark(startTime)
})

var stwSwept bool
systemstack(func() {
// 同步bytesMarked-已标记字节数
work.heap2 = work.bytesMarked
// debug.gccheckmark默认为0,忽略
if debug.gccheckmark > 0 {
startCheckmarks()
gcResetMarkState()
gcw := &getg().m.p.ptr().gcw
gcDrain(gcw, 0)
wbBufFlush1(getg().m.p.ptr())
gcw.dispose()
endCheckmarks()
}

// 同步gcphase,如果是_GCmark或_GCmarktermination,开启写屏障
setGCPhase(_GCoff)
// 启动第4阶段
stwSwept = gcSweep(work.mode)
})

mp.traceback = 0
// 非_Gscan状态转换,统计g位于_Grunnable、_Gwaiting状态时所耗费的时间
casgstatus(curgp, _Gwaiting, _Grunning)

// 重置(可抢占)
mp.preemptoff = ""

if gcphase != _GCoff {
throw("gc done but gcphase != _GCoff")
}

// 同步heap内存使用量,用于scavenge
memstats.lastHeapInUse = gcController.heapInUse.load()

// 执行gcControllerCommit
// 设置GC启动阈值、计算目标heap大小和跑道大小,计算辅助GC的工作量转换参数,计算GC触发阈值和目标heap大小,更新sweeper、scavenger的pacing参数
systemstack(gcControllerCommit)

// 更新memstats
now := nanotime() // 当前时刻(单调时钟)
sec, nsec, _ := time_now() // 获取墙上时钟
unixNow := sec*1e9 + int64(nsec) // unix时间戳
work.pauseNS += now - stw.startedStopping // STW耗时
work.tEnd = now // 第4阶段开始时刻
atomic.Store64(&memstats.last_gc_unix, uint64(unixNow)) // unix时间戳比较好理解
atomic.Store64(&memstats.last_gc_nanotime, uint64(now)) // 单调时钟
memstats.pause_ns[memstats.numgc%uint32(len(memstats.pause_ns))] = uint64(work.pauseNS)
memstats.pause_end[memstats.numgc%uint32(len(memstats.pause_end))] = uint64(unixNow)
memstats.pause_total_ns += uint64(work.pauseNS)

// CPU暂停时间GCPauseTime、GCTotalTime更新 => +=d*maxprocs
work.cpuStats.accumulateGCPauseTime(now-stw.finishedStopping, work.maxprocs)
// GC、scavenge的累计耗时更新
work.cpuStats.accumulate(now, true)

// =(GCTotalTime-GCIdleTime)/TotalTime
memstats.gc_cpu_fraction = float64(work.cpuStats.GCTotalTime-work.cpuStats.GCIdleTime) / float64(work.cpuStats.TotalTime)

// GC助攻累计时间
scavenge.assistTime.Store(0)
scavenge.backgroundTime.Store(0)

// 空闲耗时
sched.idleTime.Store(0)

// 手动启动
if work.userForced {
// 计数器更新
memstats.numforcedgc++
}

// 当前g挂起等待第n个GC周期结束
lock(&work.sweepWaiters.lock)
// gc计数器
memstats.numgc++
// 修改g状态放进本地/全局队列,并尝试唤醒m处理
injectglist(&work.sweepWaiters.list)
unlock(&work.sweepWaiters.lock)

// 更新内存回收器(scavengeIndex)版本计数器、searchAddr、freeHWM
mheap_.pages.scav.index.nextGen()

// 解除过渡状态,计算mutator耗时跟gc耗时,判断是否需要限制GC运行(加锁)
gcCPULimiter.finishGCTransition(now)

// mProfCycle计数器更新
mProf_NextCycle()

// 计数器加1,返回mheap_.sweepgen及sweepDrainedMask标记是否已设置
sl := sweep.active.begin()
// sweepDrainedMask标记已设置(表示sweeper队列为空)
if !stwSwept && !sl.valid {
throw("failed to set sweep barrier")
} else if stwSwept && sl.valid {
throw("non-concurrent sweep failed to drain all sweep queues")
}

systemstack(func() {
// epoll轮询、p数量调整、唤醒sysmon、唤醒m绑定p执行任务、统计等
startTheWorldWithSema(now, stw)
})

// mProfCycle最低位设置为1
mProf_Flush()

// 清空empty链表,将busy链表数据搬到free链表
prepareFreeWorkbufs()

// 调用freeStackSpans
// stackpool内所有allocCount为0的mspan以及stackLarge所有mspan全部释放
systemstack(freeStackSpans)

// 让p的g进入等待,强制让p进入空闲状态,执行safePoint函数
forEachP(waitReasonFlushProcCaches, func(pp *p) {
// mcache清理
// alloc列表mspan放到partial或full链表、tiny区域清空
// 清空stackcache,如果是_GCoff阶段,将空的mspan释放回mheap
pp.mcache.prepareForSweep()
if pp.status == _Pidle {
// 空闲状态
systemstack(func() {
lock(&mheap_.lock)
// 清空pageCache
pp.pcache.flush(&mheap_.pages)
unlock(&mheap_.lock)
})
}
pp.pinnerCache = nil
})

// 如果更新sl.state计数器成功
if sl.valid {
// 计数器减1
sweep.active.end(sl)
}

// arena包相关,手动管理内存,不讲
lock(&userArenaState.lock)
faultList := userArenaState.fault
userArenaState.fault = nil
unlock(&userArenaState.lock)
for _, lc := range faultList {
// 使用sysFault将mspan的内存区域标记为不可访问,更新统计信息,最后放到quarantineList列表
lc.mspan.setUserArenaChunkToFault()
}

// 如果目标heap大小超过1GB
if gcController.heapGoal() > minHeapForMetadataHugePages {
systemstack(func() {
// 相关内存如heapArena、chunk元素重新按huge_page的大小对齐(linux才有,不一定成功)
mheap_.enableMetadataHugePages()
})
}

// 恢复世界后释放,唤醒其他g,其他组件可以开始STW
semrelease(&worldsema)
semrelease(&gcsema)
// 到这里,另一个GC循环可能会开始启动

releasem(mp)
mp = nil

// concurrentSweep默认为true,故这里的判断失败,不执行
if !concurrentSweep {
// 运行finalizers
// 同协程yield关键字,当前G让出CPU,g0执行调度运行其他g,非抢占
Gosched()
}
}

第4阶段:Sweep(清理)

大概流程如下

  1. 重置mheap、sweep相关状态
  2. 非并发清理模式或手动强制GC
    • mcache清理
      • alloc列表mspan放到partial或full链表、tiny区域清空
      • 清空stackcache,如果是_GCoff阶段,将空的mspan释放回mheap
    • 不断的逐个清理mspan
      • 遍历272个mcentral,清扫每个mspan
        • 用gcmarkBits覆盖allocBits
        • 所有对象都被释放则释放整个mspan
        • 累计各种统计数据
      • 如果清扫完毕则唤醒scavenger
    • 清空empty链表,将busy链表数据搬到free链表
    • wbuf的free链表清理64个mspan、mspan内存释放回mheap
  3. 并发清理模式(默认)
    • 唤醒后台清扫器-sweeper清扫
      • 不断的逐个清理mspan
        • 遍历272个mcentral,清扫每个mspan
          • 用gcmarkBits覆盖allocBits
          • 所有对象都被释放则释放整个mspan
          • 累计各种统计数据
        • 如果清扫完毕则唤醒scavenger
      • 每完成10次mspan清扫,执行一次goschedIfBusy,当可抢占时让出CPU
      • wbuf的free链表清理64个mspan、mspan内存释放回mheap,当可抢占时让出CPU
      • 还有sweeper在运行时,继续清理mspan,否则挂起休眠
      • 清理mspan时,如果判断已经没有更多的mspan需要清理则设置完结标记,最后一个sweeper通知sysmon唤醒scavenger

当scavenger被唤醒后,大概逻辑如下

  1. scavenger开始回收,运行时间限制1ms
    1. 如果heap内存没有超过GC触发临界点且总内存没有超过限制,退出
    2. 执行mheap_.pages.scavenge,回收64KB字节数量的内存,统计累计耗时
      • 根据searchAddr从高地址chunk往低地址chunk扫描
        • 如果chunk已使用页数<496则不需要回收,否则返回chunk索引、页位置
        • searchAddr定位到chunk内最高页(chunk内从低到高扫描)
      • 在指定chunk内回收一定数量的页(最低16个页)
        • 先标记为已分配,释放回系统,最后标记为释放
    3. 如果回收字节数不足64KB,说明没有更多内存需要回收,退出
    4. 如果累计耗时超过1ms,退出
  2. 如果释放的字节数为0,挂起休眠,唤醒后重试
  3. 累计释放字节数,让出CPU挂起一段时间,更新sleepRatio等信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
// 启动清扫
func gcSweep(mode gcMode) bool {
// 空函数(staticlockranking默认为false)
assertWorldStopped()

// 非_GCoff阶段
if gcphase != _GCoff {
throw("gcSweep being done but phase is not GCoff")
}

lock(&mheap_.lock)

// 更新、重置相关字段
mheap_.sweepgen += 2 // 每次清扫完毕都+2
sweep.active.reset() // sweep.active.state设为0
mheap_.pagesSwept.Store(0)
// sweepArenas为allArenas快照(凡是heap管理的arena都会放在这里,用户手动管理则不会)
mheap_.sweepArenas = mheap_.allArenas
mheap_.reclaimIndex.Store(0)
mheap_.reclaimCredit.Store(0)

unlock(&mheap_.lock)

// 重置为0
sweep.centralIndex.clear()

// 非并发GC并发清扫模式(默认) or 手动强制执行
if !concurrentSweep || mode == gcForceBlockMode {
lock(&mheap_.lock)
// 每分配1字节需要清扫的页数
mheap_.sweepPagesPerByte = 0
unlock(&mheap_.lock)

// 遍历所有p
for _, pp := range allp {
// mcache清理
// alloc列表mspan放到partial或full链表、tiny区域清空
// 清空stackcache,如果是_GCoff阶段,将空的mspan释放回mheap
pp.mcache.prepareForSweep()
}

// 不断的逐个清理mspan
for sweepone() != ^uintptr(0) {
}

// 清空empty链表,将busy链表数据搬到free链表
prepareFreeWorkbufs()

// wbuf的free链表清理64个mspan、mspan内存释放回mheap
for freeSomeWbufs(false) {
}

// mProfCycle计数器更新
mProf_NextCycle()
// mProfCycle最低位设置为1
mProf_Flush()
return true
}

// 非默认模式手动强制执行

// 唤醒sweeper
lock(&sweep.lock)
if sweep.parked {
sweep.parked = false
// 将g放到p.runq队列头部,从空闲队列拿一个p和一个m绑定并唤醒(可能拿不到p)
ready(sweep.g, 0, true)
}
unlock(&sweep.lock)
return false
}

// 后台sweeper,被唤醒时,不停清理wbuf的mspan
func bgsweep(c chan int) {
// 纪录当前g
sweep.g = getg()

// 初始化
lockInit(&sweep.lock, lockRankSweep)
// 加锁
lock(&sweep.lock)
// 挂起
sweep.parked = true
// 发送信号给gcenable
c <- 1
// 当前g让出CPU,g0执行调度运行其他g(其内部lock会被解锁)
goparkunlock(&sweep.lock, waitReasonGCSweepWait, traceBlockGCSweep, 1)
// 被唤醒

for {
// 低优先级,尽量空闲时运行
const sweepBatchSize = 10
// 计数器
nSwept := 0
// 不断的逐个清理mspan
for sweepone() != ^uintptr(0) {
nSwept++
// 每10个执行一次goschedIfBusy
if nSwept%sweepBatchSize == 0 {
// 可抢占时让出CPU
goschedIfBusy()
}
}
// wbuf的free链表清理64个mspan、mspan内存释放回mheap
for freeSomeWbufs(true) {
// 此时free链表还有数据
// 可抢占时让出CPU
goschedIfBusy()
}

// 加锁
lock(&sweep.lock)
// sweeper数量不为0
if !isSweepDone() {
// 解锁
unlock(&sweep.lock)
// 继续清理mspan
continue
}
// 清理完毕,设置为挂起状态
sweep.parked = true

// 当前g让出CPU,g0执行调度运行其他g(其内部lock会被解锁)
goparkunlock(&sweep.lock, waitReasonGCSweepWait, traceBlockGCSweep, 1)
// 被唤醒
}
}

// 遍历272个mcentral,清扫每个mspan(用gcmarkBits覆盖allocBits,所有对象都被释放则释放整个mspan,累计各种统计数据)。如果清扫完毕则唤醒scavenger
func sweepone() uintptr {
gp := getg()

// 放置抢占
gp.m.locks++

// 计数器加1,返回mheap_.sweepgen及sweepDrainedMask标记是否已设置
sl := sweep.active.begin()
// sweeper数量为0
if !sl.valid {
// sweepDrainedMask标记已设置(表示sweeper队列为空)
gp.m.locks--
return ^uintptr(0)
}

// =2^64-1
npages := ^uintptr(0)
var noMoreWork bool
for {
// 从0到272获取mcentral,再把所有未清扫的mspan一个个返回
s := mheap_.nextSpanForSweep()
// mspan为nil,扫描完毕
if s == nil {
// 设置sweepDrainedMask标记,表示sweep已完成
noMoreWork = sweep.active.markDrained()
break
}
// 清理只针对heap管理类型,手动不管
if state := s.state.get(); state != mSpanInUse {
//
if !(s.sweepgen == sl.sweepGen || s.sweepgen == sl.sweepGen+3) {
print("runtime: bad span s.state=", state, " s.sweepgen=", s.sweepgen, " sweepgen=", sl.sweepGen, "\n")
throw("non in-use span in unswept list")
}
continue
}
// 尝试获得mspan的所有权
if s, ok := sl.tryAcquire(s); ok {
// 成功
npages = s.npages
// mspan清扫,用gcmarkBits覆盖allocBits,所有对象都被释放则释放整个mspan,累计各种统计数据
if s.sweep(false) {
// 整个mspan都被清理释放
// 回收额度/回收积分(协助g看到有额度则扣额度,没额度则协助回收)
mheap_.reclaimCredit.Add(npages)
} else {
// mspan没有被释放回mheap
// 这个mspan不回收
npages = 0
}
break
}
}
// 计数器减1
sweep.active.end(sl)

// sweep已完成
if noMoreWork {
// 最后一个sweeper负责唤醒scavenger(此时可能还有sweeper在并发运行)

// debug,忽略
if debug.scavtrace > 0 {
systemstack(func() {
lock(&mheap_.lock)

releasedBg := mheap_.pages.scav.releasedBg.Load()
releasedEager := mheap_.pages.scav.releasedEager.Load()

printScavTrace(releasedBg, releasedEager, false)

mheap_.pages.scav.releasedBg.Add(-releasedBg)
mheap_.pages.scav.releasedEager.Add(-releasedEager)
unlock(&mheap_.lock)
})
}

// 通知sysmon唤醒scavenger
scavenger.ready()
}

gp.m.locks--
return npages
}

// 所有g标记为栈未扫描、助攻积分清0,所有heapArena清除pageMarks,重置bytesMarked、同步heapLive
func gcResetMarkState() {
// allg中每个g都执行一遍该函数,期间allglock会被锁住
forEachG(func(gp *g) {
// 标记栈未扫描
gp.gcscandone = false
// Assist额度/助攻积分清0
gp.gcAssistBytes = 0
})

lock(&mheap_.lock)
// allArenas快照 => []arenaIdx
arenas := mheap_.allArenas
unlock(&mheap_.lock)

// 遍历allArenas快照
for _, ai := range arenas {
// 找到heapArena
ha := mheap_.arenas[ai.l1()][ai.l2()]
// 清除pageMarks,共1024*8=8192个位
clear(ha.pageMarks[:])
}

// 已标记字节数为0
work.bytesMarked = 0
// 同步heapLive-heap存活字节数快照
work.initialHeapLive = gcController.heapLive.Load()
}

// 清理所有mspan、重置mcentral、唤醒scavenger、gcBitsArenas迭代
func finishsweep_m() {
// 空函数(staticlockranking默认为false)
assertWorldStopped()

// 不断的逐个清理mspan
for sweepone() != ^uintptr(0) {
}

// 没有mspan可以清理了

// 从state获取sweeper数量,如果不为0,异常
if sweep.active.sweepers() != 0 {
throw("active sweepers found at start of mark phase")
}

// 重置所有未清理的mspan
sg := mheap_.sweepgen
// 遍历所有mcentral
for i := range mheap_.central {
c := &mheap_.central[i].mcentral
// 重置空的spanSet,清理残留的block
c.partialUnswept(sg).reset()
// 重置空的spanSet,清理残留的block
c.fullUnswept(sg).reset()
}

// 重置scavenger状态,修改g状态放进本地/全局队列,并尝试唤醒m处理
scavenger.wake()

// gcBitsArenas迭代,如用next替换current等
nextMarkBitArenaEpoch()
}

// 可抢占时让出CPU
func goschedIfBusy() {
gp := getg()
// 不可抢占 and 空闲p数量不为0
if !gp.preempt && sched.npidle.Load() > 0 {
return
}
// 当前g切换到g0,运行gosched_m函数
// 当前g、m解除绑定,g交给其他空闲m执行,当前m重新寻找并运行可运行的g
mcall(gosched_m)
}

// 不断执行mheap_.pages.scavenge
func bgscavenge(c chan int) {
// scavenger初始化,绑定g
scavenger.init()

// 发送信号给gcenable
c <- 1

// scavenger让出CPU挂起休眠
scavenger.park()
// 被唤醒

for {
// func (s *scavengerState) run() (released uintptr, worked float64)
// 执行mheap_.pages.scavenge,直到heap耗尽
released, workTime := scavenger.run()
// 释放字节数
if released == 0 {
// scavenger让出CPU挂起休眠
scavenger.park()
// 被唤醒
continue
}
// 累计释放字节数
mheap_.pages.scav.releasedBg.Add(released)
// 让出CPU挂起一段时间,更新sleepRatio等信息
scavenger.sleep(workTime)
}
}

混合写屏障

不知道怎么说,从源代码看,跟设计思路对不上

设计思路伪代码:

1
2
3
4
5
6
7
8
//     writePointer(slot, ptr):
// shade(*slot) // 删除写屏障
// if current stack is grey:
// shade(ptr) // 插入写屏障
// *slot = ptr
//
// *slot - 旧指针
// ptr - 新指针

实际:

在开启写屏障后,一些内存复制函数会将源内存区域跟目标内存区域内的指针对象收集起来,放入写屏障缓冲区,交给标记工作线程标记。涉及到slice、map、channel等组件,相关内存处理函数如下所示:

  • typedmemmove
  • typedslicecopy
  • typedmemclr
  • memclrHasPointers
  • makeslicecopy
  • growslice
  • reflect.typedmemclr
  • reflect.typedmemclrpartial
  • reflect.typedarrayclear

其中,typedmemmove源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// 从src复制数据到dst
func typedmemmove(typ *abi.Type, dst, src unsafe.Pointer) {
// 同一个指针
if dst == src {
return
}

// 写屏障开启 and 该类型包含指针
if writeBarrier.enabled && typ.Pointers() {
// 在src和dst内存区域内寻找指针数据,放入写屏障缓冲区
bulkBarrierPreWrite(uintptr(dst), uintptr(src), typ.PtrBytes, typ)
}

// 从src复制n个字节到dst
memmove(dst, src, typ.Size_)

// 默认为false,忽略
if goexperiment.CgoCheck2 {
cgoCheckMemmove2(typ, dst, src, 0, typ.Size_)
}
}

// 在src和dst内存区域内寻找指针数据,放入写屏障缓冲区
func bulkBarrierPreWrite(dst, src, size uintptr, typ *abi.Type) {
// 非8的倍数
if (dst|src|size)&(goarch.PtrSize-1) != 0 {
throw("bulkBarrierPreWrite: unaligned arguments")
}

// 写屏障未开启
if !writeBarrier.enabled {
return
}

// 根据地址找到heapArena再找到mspan
s := spanOf(dst)
// p可能在data、bss段上或已释放或其他内存区域如mmap共享内存
if s == nil {
// data段
for _, datap := range activeModules() {
if datap.data <= dst && dst < datap.edata {
// 根据bitmap信息在src和dst内存区域寻找指针数据,放入写屏障缓冲区
bulkBarrierBitmap(dst, src, size, dst-datap.data, datap.gcdatamask.bytedata)
return
}
}
// bss段
for _, datap := range activeModules() {
if datap.bss <= dst && dst < datap.ebss {
// 根据bitmap信息在src和dst内存区域寻找指针数据,放入写屏障缓冲区
bulkBarrierBitmap(dst, src, size, dst-datap.bss, datap.gcbssmask.bytedata)
return
}
}
return
} else if s.state.get() != mSpanInUse || dst < s.base() || s.limit <= dst {
// 手动管理内存(栈上的引用) or 指针不在mspan管理范围

// 下面三种情况之一,不管
// 1. mspan是被释放的heap空间
// 2. 是某个goroutine的栈
// 3. 是通过unsafe操作、channel发送,临时出现在其他栈上的指针
return
}
// 写屏障缓冲
buf := &getg().m.p.ptr().wbBuf

// 忽略
const doubleCheck = false
if doubleCheck {
doubleCheckTypePointersOfType(s, typ, dst, size)
}

// 根据对象的类型信息在一片内存区域内寻找指针数据,放入写屏障缓冲区

var tp typePointers
if typ != nil {
// 返回对象的起始地址及bitmap信息、对象的类型信息(大对象)
tp = s.typePointersOfType(typ, dst)
} else {
// 返回对象的起始地址及bitmap信息、对象的类型信息(小对象/对象内部)
tp = s.typePointersOf(dst, size)
}

// src为nil,只有一个数需要纪录
if src == 0 {
for {
var addr uintptr
// mask第一个uint64不为0则调用nextFast,否则移动下一个uint64再重新判断
if tp, addr = tp.next(dst + size); addr == 0 {
// 扫描到末尾还是没有
break
}
dstx := (*uintptr)(unsafe.Pointer(addr))
// 获取写屏障缓冲区可写入位置(容量为1个指针)
p := buf.get1()
// 将dstx写入到写屏障缓冲区
p[0] = *dstx
}
} else {
for {
var addr uintptr
// mask第一个uint64不为0则调用nextFast,否则移动下一个uint64再重新判断
if tp, addr = tp.next(dst + size); addr == 0 {
// 扫描到末尾还是没有
break
}
dstx := (*uintptr)(unsafe.Pointer(addr))
srcx := (*uintptr)(unsafe.Pointer(src + (addr - dst)))
// 获取写屏障缓冲区可写入位置(容量为2个指针)
p := buf.get2()
// 将dstx、srcx写入到写屏障缓冲区
p[0] = *dstx
p[1] = *srcx
}
}
}

// 根据bitmap信息在src和dst内存区域寻找指针数据,放入写屏障缓冲区
func bulkBarrierBitmap(dst, src, size, maskOffset uintptr, bits *uint8) {
// =内存字节数/8 => 共word个指针
word := maskOffset / goarch.PtrSize
// 定位到指针对应的bitmap
bits = addb(bits, word/8)
// uint8内第几位
mask := uint8(1) << (word % 8)

// 写屏障缓冲区
buf := &getg().m.p.ptr().wbBuf
// 遍历复制的字节数,步进为8
for i := uintptr(0); i < size; i += goarch.PtrSize {
// mask溢出,回到1开始
if mask == 0 {
// 下一个字节
bits = addb(bits, 1)
// 8个对象都不是指针类型
if *bits == 0 {
// 跳过这8个数据
i += 7 * goarch.PtrSize
continue
}
// 回到1开始
mask = 1
}
// 找到指针
if *bits&mask != 0 {
// 地址
dstx := (*uintptr)(unsafe.Pointer(dst + i))

// src为nil,只有一个数需要纪录
if src == 0 {
// 获取写屏障缓冲区可写入位置(容量为1个指针)
p := buf.get1()
// 将dstx写入到写屏障缓冲区
p[0] = *dstx
} else {
srcx := (*uintptr)(unsafe.Pointer(src + i))
// 获取写屏障缓冲区可写入位置(容量为2个指针)
p := buf.get2()
// 将dstx、srcx写入到写屏障缓冲区
p[0] = *dstx
p[1] = *srcx
}
}
// 往后寻找一个
mask <<= 1
}
}

相关依赖函数

GC初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// GC初始化,调度器初始化时调用
func gcinit() {
// 确保workbuf结构有2KB大小
if unsafe.Sizeof(workbuf{}) != _WorkbufSize {
throw("size of Workbuf is suboptimal")
}

// 表示sweep阶段已完成
// sweepDrainedMask = 2^31 => 2GB
sweep.active.state.Store(sweepDrainedMask)

// pacer状态初始化(gcPercent从GOGC获取,memoryLimit从GOMEMLIMIT获取)
gcController.init(readGOGC(), readGOMEMLIMIT())

// 信号量初始化
work.startSema = 1
work.markDoneSema = 1
// 锁初始化
lockInit(&work.sweepWaiters.lock, lockRankSweepWaiters)
lockInit(&work.assistQueue.lock, lockRankAssistQueue)
lockInit(&work.strongFromWeak.lock, lockRankStrongFromWeakQueue)
lockInit(&work.wbufSpans.lock, lockRankWbufSpans)
}

// 启动sweeper、scavenger
func gcenable() {
c := make(chan int, 2)
// sweeper
// 被唤醒时,不停清理wbuf的mspan
go bgsweep(c)
// scavenger
// 不断执行mheap_.pages.scavenge
go bgscavenge(c)
<-c
<-c
// 标记sweeper和scavenger已运行,可以开始运行GC
memstats.enablegc = true
}

gcTrigger-GC触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 判断是否满足条件运行GC
func (t gcTrigger) test() bool {
// 1. gcenable执行后memstats.enablegc为true,false就是还没执行
// 2. 遇到无法recover的panic时,panicking不为0
// 3. GC处于非GCoff阶段
if !memstats.enablegc || panicking.Load() != 0 || gcphase != _GCoff {
return false
}

switch t.kind {
case gcTriggerHeap: // heap达到一定大小
// 计算GC触发阈值和目标heap大小
trigger, _ := gcController.trigger()
// heap存活字节数 >=
return gcController.heapLive.Load() >= trigger

case gcTriggerTime: // 有2min没执行GC
// gcPercent默认为100
if gcController.gcPercent.Load() < 0 {
// GC被关闭,不可以执行
return false
}

// 上一次GC的时刻
lastgc := int64(atomic.Load64(&memstats.last_gc_nanotime))

// 超过2min未执行GC,可以执行
return lastgc != 0 && t.now-lastgc > forcegcperiod

case gcTriggerCycle: // 手动、强制执行
// 如果周期t.n大于全局周期work.cycles,可以执行,否则不执行
return int32(t.n-work.cycles.Load()) > 0
}

// 可以执行
return true
}

世界停止/恢复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
// 使其他p中断/停止执行g进入安全点
func stopTheWorld(reason stwReason) worldStop {
// 尝试获取worldsema,失败则挂起等待
semacquire(&worldsema)
gp := getg()
// 禁止抢占
gp.m.preemptoff = reason.String()
systemstack(func() {
// 改为_Gwaiting状态并设置waitreason,可抢占
casGToWaitingForGC(gp, _Grunning, waitReasonStoppingTheWorld)
// 将所有p都停止,返回原因、时间信息
stopTheWorldContext = stopTheWorldWithSema(reason)
// 非_Gscan状态转换,统计g位于_Grunnable、_Gwaiting状态时所耗费的时间
casgstatus(gp, _Gwaiting, _Grunning)
})
return stopTheWorldContext
}

// epoll轮询、p数量调整、唤醒sysmon、唤醒m绑定p执行任务、统计等,最后唤醒阻塞在stopTheWorldGC的g
func startTheWorld(w worldStop) {
// 执行startTheWorldWithSema
// epoll轮询、p数量调整、唤醒sysmon、唤醒m绑定p执行任务、统计等
systemstack(func() { startTheWorldWithSema(0, w) })

// 防止抢占
mp := acquirem()
// 重置(可抢占)
mp.preemptoff = ""
// 释放信号量,唤醒其他阻塞在stopTheWorldGC的g
semrelease1(&worldsema, true, 0)
releasem(mp)
}

// 使其他p中断/停止执行g进入安全点(阻塞,按顺序执行)
func stopTheWorldGC(reason stwReason) worldStop {
// 尝试获取gcsema,失败则挂起等待
semacquire(&gcsema)
// 使其他p中断/停止执行g进入安全点
return stopTheWorld(reason)
}

// epoll轮询、p数量调整、唤醒sysmon、唤醒m绑定p执行任务、统计等,最后唤醒阻塞在stopTheWorldGC的g
func startTheWorldGC(w worldStop) {
// epoll轮询、p数量调整、唤醒sysmon、唤醒m绑定p执行任务、统计等,最后唤醒阻塞在stopTheWorldGC的g
startTheWorld(w)
// 释放信号量,唤醒其他g
semrelease(&gcsema)
}

// 将所有p都停止,返回原因、时间信息
func stopTheWorldWithSema(reason stwReason) worldStop {
gp := getg()

// 禁止抢占(这里容易造成死锁)
if gp.m.locks > 0 {
throw("stopTheWorld: holding locks")
}

// 调度器加锁
lock(&sched.lock)
// 当前时刻
start := nanotime()
// 待_Pgcstop的p数量
sched.stopwait = gomaxprocs
// 调度器通知其他线程,有STW信号
sched.gcwaiting.Store(true)
// 逐个设置p的抢占标志,发送信号给线程(把其他p丢到空闲队列)
preemptall()

// 把当前p丢到空闲队列
// 状态改为_Pgcstop
gp.m.p.ptr().status = _Pgcstop
// 停止时刻
gp.m.p.ptr().gcStopTime = start
// 数量减1
sched.stopwait--

// 尝试回收正在执行系统调用的p
for _, pp := range allp {
s := pp.status
// 系统调用中,切换到_Pgcstop
if s == _Psyscall && atomic.Cas(&pp.status, s, _Pgcstop) {
// p的syscall次数+=1
pp.syscalltick++
// 停止时刻
pp.gcStopTime = nanotime()
// 数量减1
sched.stopwait--
}
}

// 已经放在空闲队列的p状态修改
now := nanotime()
for {
// 从空闲链表拿一个p,如果没拿到则通知所有m,让其中一个m让出p并进入自旋等待
pp, _ := pidleget(now)
// 没有拿到
if pp == nil {
// 退出循环
break
}
// 状态
pp.status = _Pgcstop
// 停止时刻
pp.gcStopTime = nanotime()
// 数量减1
sched.stopwait--
}

// 检查是否还有p没有放到空闲队列
wait := sched.stopwait > 0
// 解锁
unlock(&sched.lock)

// 如果还有p未停止
if wait {
for {
// 挂起休眠100us(m放在stopnote.key),标记blocked为true
if notetsleep(&sched.stopnote, 100*1000) {
// 被唤醒
// 将stopnote.key重置为0
noteclear(&sched.stopnote)
break
}
// 逐个设置p的抢占标志,发送信号给线程
preemptall()
}
}

// 结束时间
finish := nanotime()
// 耗时
startTime := finish - start
// 如果是gcStart或gcMarkDone这两个函数调用 => stwGCSweepTerm || stwGCMarkTerm
if reason.isGC() {
sched.stwStoppingTimeGC.record(startTime)
} else {
sched.stwStoppingTimeOther.record(startTime)
}

// 累计所有p的STW耗时
stoppingCPUTime := int64(0)
// 错误信息
bad := ""

if sched.stopwait != 0 {
// 仍有p未停止
bad = "stopTheWorld: not stopped (stopwait != 0)"
} else {
// 所有的p都停止了
for _, pp := range allp {
// 状态核对
if pp.status != _Pgcstop {
bad = "stopTheWorld: not stopped (status != _Pgcstop)"
}
if pp.gcStopTime == 0 && bad == "" {
bad = "stopTheWorld: broken CPU time accounting"
}
// 累计耗时
stoppingCPUTime += finish - pp.gcStopTime
// 重置为0
pp.gcStopTime = 0
}
}

// 出现无法恢复的panic
if freezing.Load() {
lock(&deadlock)
lock(&deadlock)
}
// 抛出异常
if bad != "" {
throw(bad)
}

// 空函数(staticlockranking默认为false)
worldStopped()

// 返回原因、时间信息
return worldStop{
reason: reason, // STW原因
startedStopping: start, // 函数执行的开始时刻
finishedStopping: finish, // 所有p都停止了的时刻
stoppingCPUTime: stoppingCPUTime, // 累计STW耗时
}
}

// epoll轮询、p数量调整、唤醒sysmon、唤醒m绑定p执行任务、统计等
func startTheWorldWithSema(now int64, w worldStop) int64 {
// 空函数(staticlockranking默认为false)
assertWorldStopped()

// 禁止抢占
mp := acquirem()

// netpoll已初始化
if netpollinited() {
// 执行epollWait检查,0-没有数据立即返回
list, delta := netpoll(0)
// 修改g状态放进本地/全局队列,并尝试唤醒m处理
injectglist(&list)
// 计数器+=delta => netpollWaiters+=delta
netpollAdjustWaiters(delta)
}

// 调度器加锁
lock(&sched.lock)

// CPU数量
procs := gomaxprocs
// 如果newprocs不为0,意味着用户调用GOMAXPROCS修改了p数量
if newprocs != 0 {
procs = newprocs
newprocs = 0
}

// 根据数量n扩容/缩容p
p1 := procresize(procs)
// 调度器通知其他线程,STW停止,恢复世界
sched.gcwaiting.Store(false)
// sysmon挂起休眠了
if sched.sysmonwait.Load() {
// 重置
sched.sysmonwait.Store(false)
// 唤醒sysmon(m放在sysmonnote.key)
notewakeup(&sched.sysmonnote)
}
// 解锁
unlock(&sched.lock)

// 空函数(staticlockranking默认为false)
worldStarted()

// 空闲p数量不为0
for p1 != nil {
// 获取第一个p
p := p1
// 链表调整
p1 = p1.link.ptr()
// p已被m持有
if p.m != 0 {
// m
mp := p.m.ptr()
// 移除m
p.m = 0
// nextp字段不为0
if mp.nextp != 0 {
throw("startTheWorld: inconsistent mp->nextp")
}
// 把p放到nextp字段
mp.nextp.set(p)
// 唤醒m(m放在m.park.key)
notewakeup(&mp.park)
} else {
// p未被m持有
// 清理freem链表,创建并初始化m,locked或cgo类型的m由模板线程延迟创建,其他类型则立即调用平台相关函数创建线程
newm(nil, p, -1)
}
}

// 当前时刻
if now == 0 {
now = nanotime()
}
// STW耗时
totalTime := now - w.startedStopping

// 如果是gcStart或gcMarkDone这两个函数调用 => stwGCSweepTerm || stwGCMarkTerm
if w.reason.isGC() {
sched.stwTotalTimeGC.record(totalTime)
} else {
sched.stwTotalTimeOther.record(totalTime)
}

// 从空闲队列拿一个p和一个m绑定并唤醒(可能拿不到p)
wakep()

releasem(mp)
return now
}

// 让p的g进入等待,强制让p进入空闲状态,执行safePoint函数
func forEachP(reason waitReason, fn func(*p)) {
systemstack(func() {
gp := getg().m.curg

// 改为_Gwaiting状态并设置waitreason,可抢占
casGToWaitingForGC(gp, _Grunning, reason)
// 强制让p进入空闲状态,执行safePoint函数
forEachPInternal(fn)
// 非_Gscan状态转换,统计g位于_Grunnable、_Gwaiting状态时所耗费的时间
casgstatus(gp, _Gwaiting, _Grunning)
})
}

// 强制让p进入空闲状态,执行safePoint函数
func forEachPInternal(fn func(*p)) {
// 防抢占
mp := acquirem()
// p
pp := getg().m.p.ptr()

// 调度器加锁
lock(&sched.lock)
//
if sched.safePointWait != 0 {
throw("forEachP: sched.safePointWait != 0")
}
//
sched.safePointWait = gomaxprocs - 1
//
sched.safePointFn = fn

// 让其他p执行safePoint函数
for _, p2 := range allp {
if p2 != pp {
atomic.Store(&p2.runSafePointFn, 1)
}
}

// 逐个设置p的抢占标志,发送抢占信号给线程
preemptall()

// 遍历空闲p链表
for p := sched.pidle.ptr(); p != nil; p = p.link.ptr() {
// 重置状态
if atomic.Cas(&p.runSafePointFn, 1, 0) {
// 执行safePoint函数
fn(p)
// 计数器-1
sched.safePointWait--
}
}

// 还有p未执行safePoint函数
wait := sched.safePointWait > 0
// 解锁
unlock(&sched.lock)

// 当前p执行safePoint函数
fn(pp)

// 强制将_Psyscall的p转为_Pidle并运行safePoint函数
for _, p2 := range allp {
s := p2.status

// 从_Psyscall状态改为_Pidle
if s == _Psyscall && p2.runSafePointFn == 1 && atomic.Cas(&p2.status, s, _Pidle) {
// p的syscall次数+=1
p2.syscalltick++
// 当m执行syscall或锁定时,让出p给其他m或者把p放回空闲队列
// (其内部会执行safePoint函数、更新safePointWait计数器)
handoffp(p2)
}
}

// 还有p没运行safePoint函数
if wait {
for {
// 挂起休眠100us(m放在safePointNote.key),标记blocked为true
if notetsleep(&sched.safePointNote, 100*1000) {
// 被唤醒/超时

// 将safePointNote.key重置为0
noteclear(&sched.safePointNote)
break
}

// 超时
// 逐个设置p的抢占标志,发送抢占信号给线程
preemptall()
}
}

// 异常
if sched.safePointWait != 0 {
throw("forEachP: not done")
}

// 检查每个p的状态
for _, p2 := range allp {
if p2.runSafePointFn != 0 {
throw("forEachP: P did not run fn")
}
}

// 调度器加锁
lock(&sched.lock)
// 重置函数指针
sched.safePointFn = nil
// 解锁
unlock(&sched.lock)
releasem(mp)
}

标记线程相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
// 获取快照,计算各个根对象的块数量、基地址(索引)
func gcMarkRootPrepare() {
// 空函数(staticlockranking默认为false)
assertWorldStopped()

// 计算有多少个块,每个块256KB
nBlocks := func(bytes uintptr) int {
// 按256KB的倍数向上取整
return int(divRoundUp(bytes, rootBlockBytes))
}

work.nDataRoots = 0
work.nBSSRoots = 0

// 统计data、bss的块数量
for _, datap := range activeModules() {
// 计算data包含多少个块(已初始化的全局变量和静态变量)
nDataRoots := nBlocks(datap.edata - datap.data)
if nDataRoots > work.nDataRoots {
work.nDataRoots = nDataRoots
}

// 计算bss包含多少个块(未初始化的全局变量和静态变量)
nBSSRoots := nBlocks(datap.ebss - datap.bss)
if nBSSRoots > work.nBSSRoots {
work.nBSSRoots = nBSSRoots
}
}

// 扫描mspan的finalizer、specials纪录
// markArenas为allArenas快照(凡是heap管理的arena都会放在这里,用户手动管理则不会)
mheap_.markArenas = mheap_.allArenas[:len(mheap_.allArenas):len(mheap_.allArenas)]
// =length*8192/512 = length*16
work.nSpanRoots = len(mheap_.markArenas) * (pagesPerArena / pagesPerSpanRoot)

// 扫描栈
// allgs快照
work.stackRoots = allGsSnapshot()
work.nStackRoots = len(work.stackRoots)

// 跟对象块id/计数器
work.markrootNext = 0
// 跟对象块总数 = 2+data块数量+bss块数量+mspan块数量+stack数量
// 这里有2个索引被占用了,0索引-扫描finalizer,1索引-扫描gFree.stack释放所有栈
work.markrootJobs = uint32(fixedRootCount + work.nDataRoots + work.nBSSRoots + work.nSpanRoots + work.nStackRoots)

// 计算索引
work.baseData = uint32(fixedRootCount)
work.baseBSS = work.baseData + uint32(work.nDataRoots)
work.baseSpans = work.baseBSS + uint32(work.nBSSRoots)
work.baseStacks = work.baseSpans + uint32(work.nSpanRoots)
work.baseEnd = work.baseStacks + uint32(work.nStackRoots)
}

// tiny区域对象置灰放入队列
func gcMarkTinyAllocs() {
// 空函数(staticlockranking默认为false)
assertWorldStopped()

// 遍历所有p
for _, p := range allp {
// 获取p.mcache
c := p.mcache
// 未初始化
if c == nil || c.tiny == 0 {
continue
}
// 根据p查找mspan,找到了返回对象在mspan内的位置和对象起始地址
_, span, objIndex := findObject(c.tiny, 0, 0)

// 当前p的wbuf队列
gcw := &p.gcw
// 设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
greyobject(c.tiny, 0, 0, span, gcw, objIndex)
}
}

// 清空allgs快照、gcw/mcache等残留处理
func gcMark(startTime int64) {
// 非_GCmarktermination阶段
if gcphase != _GCmarktermination {
throw("in gcMark expecting to see gcphase as _GCmarktermination")
}
work.tstart = startTime

// 确认无标记任务残留
if work.full != 0 || work.markrootNext < work.markrootJobs {
print("runtime: full=", hex(work.full), " next=", work.markrootNext, " jobs=", work.markrootJobs, " nDataRoots=", work.nDataRoots, " nBSSRoots=", work.nBSSRoots, " nSpanRoots=", work.nSpanRoots, " nStackRoots=", work.nStackRoots, "\n")
panic("non-empty mark queue after concurrent mark")
}

// debug.gccheckmark默认为0,忽略
if debug.gccheckmark > 0 {
gcMarkRootCheck()
}

// 清空allgs快照
work.stackRoots = nil

// 遍历所有p
for _, p := range allp {
// debug.gccheckmark默认为0,忽略
if debug.gccheckmark > 0 {
// 将符合条件的写屏障wbBuf数据放到wbuf,清空wbBuf
wbBufFlush1(p)
} else {
// 重置wbBuf的next、end指针
p.wbBuf.reset()
}

gcw := &p.gcw

// gcw不为空,异常
if !gcw.empty() {
printlock()
print("runtime: P ", p.id, " flushedWork ", gcw.flushedWork)
if gcw.wbuf1 == nil {
print(" wbuf1=<nil>")
} else {
print(" wbuf1.n=", gcw.wbuf1.nobj)
}
if gcw.wbuf2 == nil {
print(" wbuf2=<nil>")
} else {
print(" wbuf2.n=", gcw.wbuf2.nobj)
}
print("\n")
throw("P has cached GC work at end of mark termination")
}

// gcw为空

// wbuf1、wbuf2根据容量选择放入work.empty或work.full队列
gcw.dispose()
}

// 遍历所有p
for _, p := range allp {
// mcache
c := p.mcache
// 未初始化
if c == nil {
continue
}
// 重置
c.scanAlloc = 0
}

// 标记终止时,纪录快照,重置heapLive、heapMarked等字段
gcController.resetLive(work.bytesMarked)
}

// GC第1阶段时,清理sync.Pool、sudog缓存、defer pool及其他
func clearpools() {
// 函数不为空
if poolcleanup != nil {
// 执行
poolcleanup()
}

// 清除boringcrypto缓存
for _, p := range boringCaches {
// p置为nil
atomicstorep(p, nil)
}

// 清理unique map
if uniqueMapCleanup != nil {
select {
case uniqueMapCleanup <- struct{}{}:
default:
}
}

// 清理全局sudog缓存
// sudoglock加锁
lock(&sched.sudoglock)
var sg, sgnext *sudog
// 遍历全局sudog链表
for sg = sched.sudogcache; sg != nil; sg = sgnext {
sgnext = sg.next
// 取消链接
sg.next = nil
}
// 置为nil
sched.sudogcache = nil
// 解锁
unlock(&sched.sudoglock)

// 清空全局defer pool
// deferlock加锁
lock(&sched.deferlock)
var d, dlink *_defer
// 遍历全局defer pool
for d = sched.deferpool; d != nil; d = dlink {
dlink = d.link
// 取消链接
d.link = nil
}
// 置为nil
sched.deferpool = nil
// 解锁
unlock(&sched.deferlock)
}

// 是否还有标记任务(根对象是否扫描完,任务缓冲区是否为空)
func gcMarkWorkAvailable(p *p) bool {
// 本地任务缓冲区p.gcw不为空
if p != nil && !p.gcw.empty() {
return true
}

// 全局任务缓冲区不为空
if !work.full.empty() {
return true
}

// 根对象还没扫描完
if work.markrootNext < work.markrootJobs {
return true
}

// 标记完毕
return false
}

// gcBitsArenas迭代,如用next替换current等
func nextMarkBitArenaEpoch() {
lock(&gcBitsArenas.lock)
// 用previous替换free
if gcBitsArenas.previous != nil {
// 如果free为nil,放到free
if gcBitsArenas.free == nil {
gcBitsArenas.free = gcBitsArenas.previous
} else {
// free不为nil,previous放到free前,作为新的free

// 找到previous最后一个元素
last := gcBitsArenas.previous
for last = gcBitsArenas.previous; last.next != nil; last = last.next {
}
// previous链接free
last.next = gcBitsArenas.free
// 替换free
gcBitsArenas.free = gcBitsArenas.previous
}
}
// 用current替换previous
gcBitsArenas.previous = gcBitsArenas.current
// 用next替换current
gcBitsArenas.current = gcBitsArenas.next
// next置为nil
atomic.StorepNoWB(unsafe.Pointer(&gcBitsArenas.next), nil)
unlock(&gcBitsArenas.lock)
}

// 根据索引扫描指定的根对象,扫描的字节数除了返回还会累计到gcController
func markroot(gcw *gcWork, i uint32, flushBgCredit bool) int64 {
var workDone int64
var workCounter *atomic.Int64

switch {
case work.baseData <= i && i < work.baseBSS: // data段
workCounter = &gcController.globalsScanWork
// 遍历所有go module、动态库、plugin
for _, datap := range activeModules() {
// 扫描bss/data内存,如果是指针则设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
workDone += markrootBlock(datap.data, datap.edata-datap.data, datap.gcdatamask.bytedata, gcw, int(i-work.baseData))
}

case work.baseBSS <= i && i < work.baseSpans: // bss段
workCounter = &gcController.globalsScanWork
// // 遍历所有go module、动态库、plugin
for _, datap := range activeModules() {
// 扫描bss/data内存,如果是指针则设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
workDone += markrootBlock(datap.bss, datap.ebss-datap.bss, datap.gcbssmask.bytedata, gcw, int(i-work.baseBSS))
}

case i == fixedRootFinalizers: // i为0
// 遍历finalizer链表
for fb := allfin; fb != nil; fb = fb.alllink {
// 总数
cnt := uintptr(atomic.Load(&fb.cnt))
// 扫描一片内存,如果是指针则设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
scanblock(uintptr(unsafe.Pointer(&fb.fin[0])), cnt*unsafe.Sizeof(fb.fin[0]), &finptrmask[0], gcw, nil)
}

case i == fixedRootFreeGStacks: // i为1
// 运行markrootFreeGStacks
// 将gFree.stack的所有g的栈释放掉,然后放回gFree.noStack
systemstack(markrootFreeGStacks)

case work.baseSpans <= i && i < work.baseStacks: // mspan
// 扫描specials内存,如果是指针则设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
markrootSpans(gcw, int(i-work.baseSpans))

default: // stack
// stack扫描字节数
workCounter = &gcController.stackScanWork
// 不在stack索引范围
if i < work.baseStacks || work.baseEnd <= i {
printlock()
print("runtime: markroot index ", i, " not in stack roots range [", work.baseStacks, ", ", work.baseEnd, ")\n")
throw("markroot: bad index")
}
// 根据索引找到g
gp := work.stackRoots[i-work.baseStacks]

// 获取g.atomicstatus状态,当前非scan状态
status := readgstatus(gp)
// 阻塞时,纪录开始时刻
if (status == _Gwaiting || status == _Gsyscall) && gp.waitsince == 0 {
gp.waitsince = work.tstart
}

// 汇编,切换到g0运行,扫描stack需要切换到系统stack下运行
systemstack(func() {
// g
userG := getg().m.curg
// 扫描自身
selfScan := gp == userG && readgstatus(userG) == _Grunning
// 防止死锁
if selfScan {
// 改为_Gwaiting状态并设置waitreason,可抢占
casGToWaitingForGC(userG, _Grunning, waitReasonGarbageCollectionScan)
}

// g运行中则抢占使其停止,否则改为_Gscan状态
stopped := suspendG(gp)
// g的状态为_Gdead
if stopped.dead {
// 已经完成栈扫描
gp.gcscandone = true
return
}
// 异常状态
if gp.gcscandone {
throw("g already scanned")
}
// 扫描栈帧内指针、defer链、panic、state.buf队列
workDone += scanstack(gp, gcw)
// 已经完成栈扫描
gp.gcscandone = true
// 移除_Gscan状态,如果被抢占停止则唤醒g
resumeG(stopped)

//恢复状态
if selfScan {
// 非_Gscan状态转换,统计g位于_Grunnable、_Gwaiting状态时所耗费的时间
casgstatus(userG, _Gwaiting, _Grunning)
}
})
}
// 除非i<2,否则下面的条件一般都满足
if workCounter != nil && workDone != 0 {
// 累计到gcController对应的字段如globalsScanWork、stackScanWork
workCounter.Add(workDone)
// 一般都为true
if flushBgCredit {
// 根据额度唤醒一批g,额度有剩余就累计到全局额度bgScanCredit(与gcParkAssist成对使用)
gcFlushBgCredit(workDone)
}
}
return workDone
}

// 将gFree.stack的所有g的栈释放掉,然后放回gFree.noStack
func markrootFreeGStacks() {
// 调度器gFree加锁
lock(&sched.gFree.lock)
// 有栈
list := sched.gFree.stack
// 清空
sched.gFree.stack = gList{}
// 解锁
unlock(&sched.gFree.lock)
// 为空
if list.empty() {
return
}

// 转换类型
q := gQueue{list.head, list.head}
// 遍历gFree链表
for gp := list.head.ptr(); gp != nil; gp = gp.schedlink.ptr() {
// 释放栈
stackfree(gp.stack)
gp.stack.lo = 0
gp.stack.hi = 0
//
q.tail.set(gp)
}

// 调度器gFree加锁
lock(&sched.gFree.lock)
// 无栈
sched.gFree.noStack.pushAll(q)
// 解锁
unlock(&sched.gFree.lock)
}

// 扫描specials内存,如果是指针则设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
func markrootSpans(gcw *gcWork, shard int) {
// sweepgen
sg := mheap_.sweepgen

// 从allArenas快照找到arenaIdx =markArenas[shard/16]
ai := mheap_.markArenas[shard/(pagesPerArena/pagesPerSpanRoot)]
// 根据arenaIdx找到heapArena
ha := mheap_.arenas[ai.l1()][ai.l2()]
// =shard*512%8192
arenaPage := uint(uintptr(shard) * pagesPerSpanRoot % pagesPerArena)

// specials的bitmap
specialsbits := ha.pageSpecials[arenaPage/8:]
specialsbits = specialsbits[:pagesPerSpanRoot/8]
for i := range specialsbits {
// 8个位
specials := atomic.Load8(&specialsbits[i])
// 8个位全为0
if specials == 0 {
continue
}
// 遍历8个位
for j := uint(0); j < 8; j++ {
// 该位为0
if specials&(1<<j) == 0 {
continue
}

// 根据页索引找到mspan
s := ha.spans[arenaPage+uint(i)*8+j]

// 状态必须是mSpanInUse
if state := s.state.get(); state != mSpanInUse {
print("s.state = ", state, "\n")
throw("non in-use span found with specials bit set")
}
// 确保mspan已经清理
if !useCheckmark && !(s.sweepgen == sg || s.sweepgen == sg+3) {
print("sweep ", s.sweepgen, " ", sg, "\n")
throw("gc: unswept span")
}

// 加锁,防止special在遍历时被移除
lock(&s.speciallock)
// 遍历specials
for sp := s.specials; sp != nil; sp = sp.next {
switch sp.kind {
case _KindSpecialFinalizer: // finalizer
// 不标记、只扫描
spf := (*specialfinalizer)(unsafe.Pointer(sp))
// 找到这个对象
p := s.base() + uintptr(spf.special.offset)/s.elemsize*s.elemsize

// 标记从这个对象出发可达的所有对象(不包括当前对象)
if !s.spanclass.noscan() {
// 扫描一个对象(最多128KB,剩余放到任务缓冲区)内所有指针,设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
scanobject(p, gcw)
}

// special本身是一个根对象
// 扫描一片内存,如果是指针则设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
scanblock(uintptr(unsafe.Pointer(&spf.fn)), goarch.PtrSize, &oneptrmask[0], gcw, nil)
case _KindSpecialWeakHandle: // weak handle
spw := (*specialWeakHandle)(unsafe.Pointer(sp))
// special本身是一个根对象
// 扫描一片内存,如果是指针则设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
scanblock(uintptr(unsafe.Pointer(&spw.handle)), goarch.PtrSize, &oneptrmask[0], gcw, nil)
case _KindSpecialCleanup: // cleanup
spc := (*specialCleanup)(unsafe.Pointer(sp))
// special本身是一个根对象
// 扫描一片内存,如果是指针则设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
scanblock(uintptr(unsafe.Pointer(&spc.fn)), goarch.PtrSize, &oneptrmask[0], gcw, nil)
}
}
// 解锁
unlock(&s.speciallock)
}
}
}

// 扫描bss/data内存,如果是指针则设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
func markrootBlock(b0, n0 uintptr, ptrmask0 *uint8, gcw *gcWork, shard int) int64 {
// 256KB => 262144
if rootBlockBytes%(8*goarch.PtrSize) != 0 {
throw("rootBlockBytes must be a multiple of 8*ptrSize")
}

// shard => bss/data块的起始索引,每个块有256KB,用于迅速定位内存块
off := uintptr(shard) * rootBlockBytes
// b0 => bss/data内存起始地址,n0 => bss/data内存终止地址
if off >= n0 {
// 越界
return 0
}

// 块起始地址
b := b0 + off

// 这个块的bitmap起始地址(整个bss/data区域有4KB的bitmap)
ptrmask := (*uint8)(add(unsafe.Pointer(ptrmask0), uintptr(shard)*(rootBlockBytes/(8*goarch.PtrSize))))

// 256KB
n := uintptr(rootBlockBytes)
// 越界
if off+n > n0 {
// 调整要扫描的字节数,=min(n0-off, 256KB)
n = n0 - off
}

// 扫描一片内存,如果是指针则设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
scanblock(b, n, ptrmask, gcw, nil)
return int64(n)
}

// 扫描一片内存,如果是指针则设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
func scanblock(b0, n0 uintptr, ptrmask *uint8, gcw *gcWork, stk *stackScanState) {
// 复制,debug时可以查看初始值,用于参考
b := b0
n := n0

// 扫描n个字节
for i := uintptr(0); i < n; {
// 调整指针指向正确的mask => ptrmask+i/64,uint8扩展成uint32
bits := uint32(*addb(ptrmask, i/(goarch.PtrSize*8)))

// 8个对象都不是指针类型
if bits == 0 {
i += goarch.PtrSize * 8
continue
}

// 8个对象中,至少有一个是指针

// 遍历uint8的8个位
for j := 0; j < 8 && i < n; j++ {
// 该对象是指针
if bits&1 != 0 {
// 下面同scanobject

// 读取指针位置的内容
p := *(*uintptr)(unsafe.Pointer(b + i))
if p != 0 {
// 需要进一步判断是不是指针类型

// 根据p查找mspan,找到了返回对象在mspan内的位置和对象起始地址
if obj, span, objIndex := findObject(p, b, i); obj != 0 {
// 设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
greyobject(obj, b, i, span, gcw, objIndex)
} else if stk != nil && p >= stk.stack.lo && p < stk.stack.hi {
// 如果在mspan内找不到,检查这个地址是不是在栈内
// 在栈内则把指针放到stk.buf队列
stk.putPtr(p, false)
}

// 不管是heap管理还是手动管理的mspan都没找到
}
}
// 右移1位
bits >>= 1
// 指针大小
i += goarch.PtrSize
}
}
}

// 扫描一个对象(最多128KB,剩余放到任务缓冲区)内所有指针,设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
func scanobject(b uintptr, gcw *gcWork) {
// 提前加载数据到CPU缓存
sys.Prefetch(b)

// 根据地址找到heapArena再找到mspan
s := spanOfUnchecked(b)
// 对象大小
n := s.elemsize
// 为0,异常
if n == 0 {
throw("scanobject n == 0")
}
// noscan,不需要扫描
if s.spanclass.noscan() {
throw("scanobject of a noscan object")
}

var tp typePointers
// 大对象 elemsize>128KB => 131072
if n > maxObletBytes {
// 先将整个mspan切块
if b == s.base() {
// 按128KB分块,保留第一个块,剩下放到wbuf
for oblet := b + maxObletBytes; oblet < s.base()+s.elemsize; oblet += maxObletBytes {
// 将对象放到wbuf-任务缓冲区,未初始化或已满直接返回
if !gcw.putFast(oblet) {
// 将对象放到wbuf-任务缓冲区,已满则从work.empty获取一个wbuf替换用
gcw.put(oblet)
}
}
}

// b到limit的字节数
n = s.base() + s.elemsize - b
// n最多128KB =min(n,131072)
n = min(n, maxObletBytes)
// 返回对象的起始地址及bitmap信息、对象的类型信息
tp = s.typePointersOfUnchecked(s.base())
// 地址往后移动n字节,获取实际对象地址和bitmap
tp = tp.fastForward(b-tp.addr, b+n)
} else {
// elemsize<=128KB
// 返回对象的起始地址及bitmap信息、对象的类型信息
tp = s.typePointersOfUnchecked(b)
}

var scanSize uintptr
for {
var addr uintptr
// 将mask第一个uint64内的第一个指针的位置为0,返回修改后的tp和指针地址
if tp, addr = tp.nextFast(); addr == 0 {
// uint64没数据了

// mask第一个uint64不为0则调用nextFast,否则移动下一个uint64再重新判断
if tp, addr = tp.next(b + n); addr == 0 {
// 扫描到末尾还是没有
break
}
}

// 已扫描字节数 =addr-b+8 => =scalar类型占用的字节数+8字节指针
scanSize = addr - b + goarch.PtrSize

// 对象指针
obj := *(*uintptr)(unsafe.Pointer(addr))

// 指针不为0 and 超过对象大小或超过一个块大小
if obj != 0 && obj-b >= n {
// 指针不为0时需要进一步判断是不是指针类型

// 根据p查找mspan,找到了返回对象在mspan内的位置和对象起始地址
if obj, span, objIndex := findObject(obj, b, addr-b); obj != 0 {
// 设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
greyobject(obj, b, addr-b, span, gcw, objIndex)
}
}
}
// 被标记的字节总数
gcw.bytesMarked += uint64(n)
// 被扫描字节数
gcw.heapScanWork += int64(scanSize)
}

// 根据p查找mspan,找到了返回对象在mspan内的位置和对象起始地址
func findObject(p, refBase, refOff uintptr) (base uintptr, s *mspan, objIndex uintptr) {
// 根据地址找到heapArena再找到mspan
s = spanOf(p)
// p可能在data、bss段上或已释放或其他内存区域如mmap共享内存
if s == nil {
if (GOARCH == "amd64" || GOARCH == "arm64") && p == clobberdeadPtr && debug.invalidptr != 0 {
badPointer(s, p, refBase, refOff)
}
return
}

// 手动管理内存(栈上的引用) or 指针不在mspan管理范围
if state := s.state.get(); state != mSpanInUse || p < s.base() || p >= s.limit {
// 手动管理内存
if state == mSpanManual {
// 此时base、objIndex为0
return
}

// 下面三种情况之一,不管
// 1. mspan是被释放的heap空间
// 2. 是某个goroutine的栈
// 3. 是通过unsafe操作、channel发送,临时出现在其他栈上的指针

if debug.invalidptr != 0 {
badPointer(s, p, refBase, refOff)
}
return
}

// 对象在mspan内的位置
// =(p-s.base)/s.elemsize
objIndex = s.objIndex(p)
// 对象起始地址
base = s.base() + objIndex*s.elemsize
return
}

// 设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
func greyobject(obj, base, off uintptr, span *mspan, gcw *gcWork, objIndex uintptr) {
//base和off用于debug,可以忽略

// 对象是否是8的倍数
if obj&(goarch.PtrSize-1) != 0 {
throw("greyobject: obj not pointer-aligned")
}

// 获取该对象的bitmap(从gcmarkBits获取,第几个字节、字节内第几位)
mbits := span.markBitsForIndex(objIndex)

// debug.gccheckmark默认为0,忽略
if useCheckmark {
// debug.gccheckmark不为0
if setCheckmark(obj, base, off, mbits) {
return
}
} else {
// 看这里

// debug.gccheckmark默认为0,忽略
if debug.gccheckmark > 0 && span.isFree(objIndex) {
print("runtime: marking free object ", hex(obj), " found at *(", hex(base), "+", hex(off), ")\n")
gcDumpObject("base", base, off)
gcDumpObject("obj", obj, ^uintptr(0))
getg().m.traceback = 2
throw("marking free object")
}

// 该位已经设置为1
if mbits.isMarked() {
// 已设置,不处理
return
}
// 将该位设置为1(只要用mask跟gcmarkBits按位或)
mbits.setMarked()

// 根据mspan起始地址获取heapArena、页索引、页bitmap位置
arena, pageIdx, pageMask := pageIndexOf(span.base())
// 将这个页的bitmap设置为1(粗略,只设置一页,不是所有页,此外,pageMarks在GC启动时清0)
if arena.pageMarks[pageIdx]&pageMask == 0 {
atomic.Or8(&arena.pageMarks[pageIdx], pageMask)
}

// mspan是scalar类型,到这里结束,否则放进wbuf缓冲区
if span.spanclass.noscan() {
// 对象大小累计到bytesMarked-被标记的字节总数
gcw.bytesMarked += uint64(span.elemsize)
return
}
}

// 提前加载数据到CPU缓存
sys.Prefetch(obj)
// 将对象放到wbuf-任务缓冲区,未初始化或已满直接返回
if !gcw.putFast(obj) {
// 将对象放到wbuf-任务缓冲区,已满则从work.empty获取一个wbuf替换用
gcw.put(obj)
}
}

清扫器相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
// 计数器加1,返回mheap_.sweepgen及sweepDrainedMask标记是否已设置
func (a *activeSweep) begin() sweepLocker {
for {
state := a.state.Load()
// 最高位不为0,队列已经没有sweeper
if state&sweepDrainedMask != 0 {
return sweepLocker{mheap_.sweepgen, false}
}
// 计数器+1
if a.state.CompareAndSwap(state, state+1) {
// 成功
return sweepLocker{mheap_.sweepgen, true}
}
// 失败重试
}
}

// 计数器减1
func (a *activeSweep) end(sl sweepLocker) {
if sl.sweepGen != mheap_.sweepgen {
throw("sweeper left outstanding across sweep generations")
}
for {
state := a.state.Load()
// 丢弃最高位后减1
if (state&^sweepDrainedMask)-1 >= sweepDrainedMask {
throw("mismatched begin/end of activeSweep")
}
// 计数器减1
if a.state.CompareAndSwap(state, state-1) {
// 成功
// 下面跟debug有关,忽略
if state != sweepDrainedMask {
return
}
// debug,忽略
if debug.gcpacertrace > 0 {
live := gcController.heapLive.Load()
print("pacer: sweep done at heap size ", live>>20, "MB; allocated ", (live-mheap_.sweepHeapLiveBasis)>>20, "MB during sweep; swept ", mheap_.pagesSwept.Load(), " pages at ", mheap_.sweepPagesPerByte, " pages/byte\n")
}
return
}
// 失败重试
}
}

// 设置sweepDrainedMask标记,表示sweep已完成
func (a *activeSweep) markDrained() bool {
for {
state := a.state.Load()
// 已设置
if state&sweepDrainedMask != 0 {
return false
}
// 设置sweepDrainedMask
if a.state.CompareAndSwap(state, state|sweepDrainedMask) {
return true
}
}
}

// 尝试获得mspan的所有权
func (l *sweepLocker) tryAcquire(s *mspan) (sweepLocked, bool) {
// sweepDrainedMask标记已设置(表示sweeper队列为空)
if !l.valid {
throw("use of invalid sweepLocker")
}
// 状态不符
if atomic.Load(&s.sweepgen) != l.sweepGen-2 {
// 失败
return sweepLocked{}, false
}
// 从sweepGen-2改为sweepGen-1
if !atomic.Cas(&s.sweepgen, l.sweepGen-2, l.sweepGen-1) {
// 更新失败
return sweepLocked{}, false
}
// 更新成功
return sweepLocked{s}, true
}

// mspan清扫,用gcmarkBits覆盖allocBits,所有对象都被释放则释放整个mspan,累计各种统计数据
func (sl *sweepLocked) sweep(preserve bool) bool {
gp := getg()
// 禁止抢占标记
if gp.m.locks == 0 && gp.m.mallocing == 0 && gp != gp.m.g0 {
throw("mspan.sweep: m is not locked")
}

s := sl.mspan
// 如果不保留(一般情况下都是不保留,除了mcentral.cacheSpan)
if !preserve {
// 释放所有权
sl.mspan = nil
}

//
sweepgen := mheap_.sweepgen
// 状态异常
if state := s.state.get(); state != mSpanInUse || s.sweepgen != sweepgen-1 {
print("mspan.sweep: state=", state, " sweepgen=", s.sweepgen, " mheap.sweepgen=", sweepgen, "\n")
throw("mspan.sweep: bad span state")
}

// 已完成清扫的页数量(整个mspan管理的内存大小)
mheap_.pagesSwept.Add(int64(s.npages))

spc := s.spanclass
size := s.elemsize

// 1. 一个对象可同时拥有finalizer、profile,finalizer会加入队列,而profile则会被保留
// 2. tiny对象可以有多个finalizer作用在不同的offset,需要一次性将所有finalizer加入队列
hadSpecials := s.specials != nil
// 纪录specials
siter := newSpecialsIter(s)

// specials不为nil
for siter.valid() {
// 对象起始索引
objIndex := uintptr(siter.s.offset) / size
// 对象起始地址
p := s.base() + objIndex*size
// 获取该对象的bitmap(从gcmarkBits获取,第几个字节、字节内第几位)
mbits := s.markBitsForIndex(objIndex)

// 如果该位还没有设置
if !mbits.isMarked() {
// 检查是否有finalizer
hasFinAndRevived := false
// 终止地址的offset
endOffset := p - s.base() + size
// 寻找finalizer
for tmp := siter.s; tmp != nil && uintptr(tmp.offset) < endOffset; tmp = tmp.next {
// 找到有finalizer
if tmp.kind == _KindSpecialFinalizer {
// bitmap该位设置为1(复活这个对象)
mbits.setMarkedNonAtomic()
hasFinAndRevived = true
break
}
}

// 有finalizer
if hasFinAndRevived {
// 把所有finalizer加入队列,清除所有weak handle

// 扫描所有与对象相关的special纪录
for siter.valid() && uintptr(siter.s.offset) < endOffset {
//
special := siter.s
// 对象地址
p := s.base() + uintptr(special.offset)
// finalizer or weak handle
if special.kind == _KindSpecialFinalizer || special.kind == _KindSpecialWeakHandle {
// 把当前节点从链表里移除
siter.unlinkAndNext()
// 释放special(不同类型的处理方式不同,有些实际上不需要释放)
// 如果是finalizer,则将finalizer信息加入队列,然后释放
// 如果是weak handle,则重置相关字段,然后释放
freeSpecial(special, unsafe.Pointer(p), size)
} else {
// 保留,其他类型的special都只会在对象释放时执行
siter.next()
}
}
} else {
// 没有finalizer
// 对象已死,释放所有special

// 扫描所有与对象相关的special纪录
for siter.valid() && uintptr(siter.s.offset) < endOffset {
special := siter.s
p := s.base() + uintptr(special.offset)
// 把当前节点从链表里移除
siter.unlinkAndNext()
// 释放special(不同类型的处理方式不同,有些实际上不需要释放)
freeSpecial(special, unsafe.Pointer(p), size)
}
}
} else {
// 如果该位为1,寻找reachable类型的special

// 如果找到的是reachable(对象仍然存活)
if siter.s.kind == _KindSpecialReachable {
// 把当前节点从链表里移除
special := siter.unlinkAndNext()

// 以下两行代码更新reachable和done字段,不需要释放
(*specialReachable)(unsafe.Pointer(special)).reachable = true
// 释放special(不同类型的处理方式不同,有些实际上不需要释放)
freeSpecial(special, unsafe.Pointer(p), size)
} else {
// 扫描下一个
siter.next()
}
}
}

// special已全部释放
if hadSpecials && s.specials == nil {
// 更新special的bitmap(不想深入)
spanHasNoSpecials(s)
}

// 检查僵尸对象
if s.freeindex < s.nelems {
// <freeindex的对象不能是僵尸对象

// 同freeindex
obj := uintptr(s.freeindex)
// gc标记了,但alloc没标记
if (*s.gcmarkBits.bytep(obj / 8)&^*s.allocBits.bytep(obj / 8))>>(obj%8) != 0 {
// 打印mspann内标记但已释放的对象并抛出异常
s.reportZombies()
}
// 检查剩余字节
for i := obj/8 + 1; i < divRoundUp(uintptr(s.nelems), 8); i++ {
if *s.gcmarkBits.bytep(i)&^*s.allocBits.bytep(i) != 0 {
// 打印mspann内标记但已释放的对象并抛出异常
s.reportZombies()
}
}
}

// 通过gcmarkBits计算已分配对象数
nalloc := uint16(s.countAlloc())
// 释放的对象数量
nfreed := s.allocCount - nalloc
// 异常
if nalloc > s.allocCount {
// 僵尸对象检查应该已经解决这个问题
print("runtime: nelems=", s.nelems, " nalloc=", nalloc, " previous allocCount=", s.allocCount, " nfreed=", nfreed, "\n")
throw("sweep increased allocation count")
}

// 用gc结果覆盖
s.allocCount = nalloc
// 重置
s.freeindex = 0
s.freeIndexForScan = 0

// 使用gcmarkBits替换allocBits
s.allocBits = s.gcmarkBits
// 从gcBitsArenas分配足以容纳nelems个位的内存(64的倍数向上取整)
s.gcmarkBits = newMarkBits(uintptr(s.nelems))

// 不讨论
if s.pinnerBits != nil {
s.refreshPinnerBits()
}

// 从s.allocBits分配8个字节替换为新的s.allocCache
s.refillAllocCache(0)

// sweepgen更新前检查
if state := s.state.get(); state != mSpanInUse || s.sweepgen != sweepgen-1 {
print("mspan.sweep: state=", state, " sweepgen=", s.sweepgen, " mheap.sweepgen=", sweepgen, "\n")
throw("mspan.sweep: bad span state after sweep")
}
if s.sweepgen == sweepgen+1 || s.sweepgen == sweepgen+3 {
throw("swept cached span")
}

// 更新为mheap.sweepgen
atomic.Store(&s.sweepgen, sweepgen)

// 归属于arena包,用户手动管理,暂时忽略
if s.isUserArenaChunk {
// 保留,异常
if preserve {
throw("sweep: tried to preserve a user arena span")
}
// 还有存活对象
if nalloc > 0 {
// mspan还有指针、未释放,放入full swept链表
mheap_.central[spc].mcentral.fullSwept(sweepgen).push(s)
return false
}

// 没有存活对象

// heap内存页使用量
mheap_.pagesInUse.Add(-s.npages)
// 标记死亡
s.state.set(mSpanDead)

// 从quarantineList移除,放入readyList
systemstack(func() {
// 异常
if s.list != &mheap_.userArena.quarantineList {
throw("user arena span is on the wrong list")
}
lock(&mheap_.lock)
mheap_.userArena.quarantineList.remove(s)
mheap_.userArena.readyList.insert(s)
unlock(&mheap_.lock)
})
return false
}

// heap管理

// 小对象
if spc.sizeclass() != 0 {
// 有nfreed个对象被释放
if nfreed > 0 {
// 标记为需要清0
s.needzero = 1

// statsSeq计数器加1 => 奇数,表示p正在写入stats
// =stats[gen%3] => heapStatsDelta
stats := memstats.heapStats.acquire()
// 累计回收对象数量
atomic.Xadd64(&stats.smallFreeCount[spc.sizeclass()], int64(nfreed))
// statsSeq计数器加1 => 偶数
memstats.heapStats.release()

// 累计回收对象数量
gcController.totalFree.Add(int64(nfreed) * int64(s.elemsize))
}

// 不保留(后台清扫线程,保留的是mcentral分配器)
if !preserve {
// 全部对象都已释放
if nalloc == 0 {
// 释放页和mspan
mheap_.freeSpan(s)
return true
}

if nalloc == s.nelems {
// mspan已满
// 放入full swept链表
mheap_.central[spc].mcentral.fullSwept(sweepgen).push(s)
} else {
// mspan未满
// 放入partial swept链表
mheap_.central[spc].mcentral.partialSwept(sweepgen).push(s)
}
}
} else if !preserve {
// 大对象 and 不保留

// mspan只有一个对象,nfreed不为0就是被回收了
if nfreed != 0 {
// statsSeq计数器加1 => 奇数,表示p正在写入stats
// =stats[gen%3] => heapStatsDelta
stats := memstats.heapStats.acquire()
// 大对象独占一个mspan
atomic.Xadd64(&stats.largeFreeCount, 1)
// 回收字节数
atomic.Xadd64(&stats.largeFree, int64(size))
// statsSeq计数器加1 => 偶数
memstats.heapStats.release()

// 累计回收对象数量
gcController.totalFree.Add(int64(size))

// debug,忽略
if debug.efence > 0 {
s.limit = 0 // prevent mlookup from finding this span
sysFault(unsafe.Pointer(s.base()), size)
} else {
// 直接看这里

// 释放页和mspan
mheap_.freeSpan(s)
}
return true
}

// 无对象释放

// 放入full swept链表
mheap_.central[spc].mcentral.fullSwept(sweepgen).push(s)
}

// 不释放回mheap
return false
}

内存回收器相关

scavengerState-节奏调度器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// scavenger初始化,绑定g
func (s *scavengerState) init() {
if s.g != nil {
throw("scavenger state is already wired")
}

// 初始化
lockInit(&s.lock, lockRankScavenge)
// 绑定g
s.g = getg()

// 定时器
s.timer = new(timer)
// 过期函数
f := func(s any, _ uintptr, _ int64) {
// 重置scavenger状态,修改g状态放进本地/全局队列,并尝试唤醒m处理
s.(*scavengerState).wake()
}
// mu/f/arg参数初始化
s.timer.init(f, s)

//
s.sleepController = piController{
// Tuned loosely via Ziegler-Nichols process.
kp: 0.3375,
ti: 3.2e6,
tt: 1e9, // 1s

//
min: 0.001, // 1:1000
max: 1000.0, // 1000:1
}
// =0.001
s.sleepRatio = startingScavSleepRatio

// 未初始化
if s.scavenge == nil {
s.scavenge = func(n uintptr) (uintptr, int64) {
// 当前时刻
start := nanotime()
// func (p *pageAlloc) scavenge(nbytes uintptr, shouldStop func() bool, force bool) uintptr
// 回收指定字节数量的内存,先标记为已分配,释放回系统,最后标记为释放(扫描时是从高地址向低地址进行搜索)
r := mheap_.pages.scavenge(n, nil, false)
// 当前时刻
end := nanotime()
//
if start >= end {
return r, 0
}
//
scavenge.backgroundTime.Add(end - start)
return r, end - start
}
}
// scavenger是否需要停止
if s.shouldStop == nil {
s.shouldStop = func() bool {
// heap内存没有超过GC触发临界点 and 总内存没有超过限制
return heapRetained() <= scavenge.gcPercentGoal.Load() &&
gcController.mappedReady.Load() <= scavenge.memoryLimitGoal.Load()
}
}
// 测试用
if s.gomaxprocs == nil {
s.gomaxprocs = func() int32 {
return gomaxprocs
}
}
}

// scavenger让出CPU挂起休眠
func (s *scavengerState) park() {
lock(&s.lock)
// 其他g调用
if getg() != s.g {
throw("tried to park scavenger from another goroutine")
}
// 表示挂起
s.parked = true
// 当前g让出CPU,g0执行调度运行其他g(在内部g、m解除绑定后会解锁lock)
goparkunlock(&s.lock, waitReasonGCScavengeWait, traceBlockSystemGoroutine, 2)
// 被唤醒
}

// 通知sysmon唤醒scavenger
func (s *scavengerState) ready() {
s.sysmonWake.Store(1)
}

// 重置scavenger状态,修改g状态放进本地/全局队列,并尝试唤醒m处理
func (s *scavengerState) wake() {
lock(&s.lock)
// scavenger是否挂起休眠中
if s.parked {
// 重置sysmonWake
s.sysmonWake.Store(0)

// 重置parked
s.parked = false

var list gList
list.push(s.g)
// 修改g状态放进本地/全局队列,并尝试唤醒m处理
injectglist(&list)
}
unlock(&s.lock)
}

// 让出CPU挂起一段时间,更新sleepRatio等信息
func (s *scavengerState) sleep(worked float64) {
lock(&s.lock)
if getg() != s.g {
throw("tried to sleep scavenger from another goroutine")
}

// worked最低为1ms
if worked < minScavWorkTime {
worked = minScavWorkTime
}

// =worked*1.7
worked *= 1 + scavengeCostRatio

// =worked/sleepRatio => sleepRatio默认初始值为0.001
sleepTime := int64(worked / s.sleepRatio)

var slept int64
// 一般情况下都是nil
if s.sleepStub == nil {
// 设置定时器

// 开始时刻
start := nanotime()
// 重置,设置睡眠时间
s.timer.reset(start+sleepTime, 0)

// 表示挂起
s.parked = true
// 当前g让出CPU,g0执行调度运行其他g(在内部g、m解除绑定后会解锁lock)
goparkunlock(&s.lock, waitReasonSleep, traceBlockSleep, 2)
// 被唤醒

// 计算休眠耗时
slept = nanotime() - start

lock(&s.lock)
// 停止定时器,不管是否成功
s.timer.stop()
unlock(&s.lock)
} else {
unlock(&s.lock)
slept = s.sleepStub(sleepTime)
}

//
if s.controllerCooldown > 0 {
//
t := slept + int64(worked)
if t > s.controllerCooldown {
s.controllerCooldown = 0
} else {
s.controllerCooldown -= t
}
return
}

// =1/100
idealFraction := float64(scavengePercent) / 100.0

// 计算CPU耗时
cpuFraction := worked / ((float64(slept) + worked) * float64(s.gomaxprocs()))

var ok bool
//
s.sleepRatio, ok = s.sleepController.next(cpuFraction, idealFraction, float64(slept)+worked)
if !ok {
// 失败
// =0.001
s.sleepRatio = startingScavSleepRatio
// 5秒
s.controllerCooldown = 5e9

// 将printControllerReset置为true
s.controllerFailed()
}
}

// 执行mheap_.pages.scavenge,直到heap耗尽
func (s *scavengerState) run() (released uintptr, worked float64) {
lock(&s.lock)
if getg() != s.g {
throw("tried to run scavenger from another goroutine")
}
unlock(&s.lock)

// <1ms
for worked < minScavWorkTime {
// 如果scavenger需要停止
if s.shouldStop() {
break
}

// 64KB
const scavengeQuantum = 64 << 10

// 执行mheap_.pages.scavenge,回收指定字节数量的内存,统计累计耗时
r, duration := s.scavenge(scavengeQuantum)

// 假设每个物理页需耗时10µs
const approxWorkedNSPerPhysicalPage = 10e3
if duration == 0 {
// 耗时为0
// 总耗时+=10µs*r/physPageSize
worked += approxWorkedNSPerPhysicalPage * float64(r/physPageSize)
} else {
// 耗时不为0
// 直接累计
worked += float64(duration)
}
// 释放字节数
released += r

// r<64KB
if r < scavengeQuantum {
// heap已经耗尽
break
}
// 使用faketime时,只执行一次
if faketime != 0 {
break
}
}
// 期望是整页,这里不足1页,异常
if released > 0 && released < physPageSize {
throw("released less than one physical page of memory")
}
return
}

scavengeIndex-回收索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
// 初始化
func (s *scavengeIndex) init(test bool, sysStat *sysMemStat) uintptr {
s.searchAddrBg.Clear() // 设为0
s.searchAddrForce.Clear() // 设为0
s.freeHWM = minOffAddr // 最小地址0xffff800000000000
s.test = test
// 从系统申请内存(Reserved)初始化chunks(512MB)
return s.sysInit(test, sysStat)
}

// 从系统申请内存(Reserved)初始化chunks(512MB)
func (s *scavengeIndex) sysInit(test bool, sysStat *sysMemStat) uintptr {
// =1<<48/4194304 => 最大地址能分多少个chunk => 64M个chunk
n := uintptr(1<<heapAddrBits) / pallocChunkBytes
// =n*8 => 512MB
nbytes := n * unsafe.Sizeof(atomicScavChunkData{})
// 向系统申请内存(Reserved)
r := sysReserve(nil, nbytes)
sl := notInHeapSlice{(*notInHeap)(r), int(n), int(n)}
s.chunks = *(*[]atomicScavChunkData)(unsafe.Pointer(&sl))
return 0
}

// 更新minHeapIdx,分配/映射物理内存使地址变为可用
func (s *scavengeIndex) grow(base, limit uintptr, sysStat *sysMemStat) uintptr {
// 更新minHeapIdx
minHeapIdx := s.minHeapIdx.Load()
if baseIdx := uintptr(chunkIndex(base)); minHeapIdx == 0 || baseIdx < minHeapIdx {
// 不为0时=min(minHeapIdx,baseIdx)
s.minHeapIdx.Store(baseIdx)
}
// 在指定的虚拟地址范围内分配/映射物理内存,让该地址变为可用
return s.sysGrow(base, limit, sysStat)
}

// 在指定的虚拟地址范围内分配/映射物理内存,让该地址变为可用
func (s *scavengeIndex) sysGrow(base, limit uintptr, sysStat *sysMemStat) uintptr {
// 4MB对齐
if base%pallocChunkBytes != 0 || limit%pallocChunkBytes != 0 {
print("runtime: base = ", hex(base), ", limit = ", hex(limit), "\n")
throw("sysGrow bounds not aligned to pallocChunkBytes")
}

// sc大小
scSize := unsafe.Sizeof(atomicScavChunkData{})

//
haveMin := s.min.Load()
haveMax := s.max.Load()
// 向下取整
needMin := alignDown(uintptr(chunkIndex(base)), physPageSize/scSize)
// 向上取整
needMax := alignUp(uintptr(chunkIndex(limit)), physPageSize/scSize)

//
if needMax < haveMin {
needMax = haveMin
}
if haveMax != 0 && needMin > haveMax {
needMin = haveMax
}

// 第一个chunk
chunksBase := uintptr(unsafe.Pointer(&s.chunks[0]))
// 封装base、limit
have := makeAddrRange(chunksBase+haveMin*scSize, chunksBase+haveMax*scSize)
need := makeAddrRange(chunksBase+needMin*scSize, chunksBase+needMax*scSize)

// 1. 根据地址计算出起始、终止索引
// 2. need根据have的地址范围调整base和limit
need = need.subtract(have)

// 如果need的地址范围被包含在have的地址范围内时,base和limit设置为0
if need.size() != 0 {
// 使用sysMap直接映射,内存状态从Reserved改为Prepared
sysMap(unsafe.Pointer(need.base.addr()), need.size(), sysStat)
// 内存状态从Prepared改为Ready
sysUsed(unsafe.Pointer(need.base.addr()), need.size(), need.size())

//
if haveMax == 0 || needMin < haveMin {
s.min.Store(needMin)
}
if needMax > haveMax {
s.max.Store(needMax)
}
}

// 扩容字节大小
return need.size()
}

// 返回可能含有可清理页的chunk的最大索引(扫描时是从高地址向低地址进行搜索)
func (s *scavengeIndex) find(force bool) (chunkIdx, uint) {
// 根据force选择搜索地址
cursor := &s.searchAddrBg
if force {
cursor = &s.searchAddrForce
}

// 下面负数表示marked,绝对值为地址
searchAddr, marked := cursor.Load()
// 如果是最小地址0xffff800000000000
if searchAddr == minOffAddr.addr() {
return 0, 0
}

gen := s.gen
// 最小chunk索引
min := chunkIdx(s.minHeapIdx.Load())
// 搜索地址chunk索引
start := chunkIndex(searchAddr)
// 从高到低扫描
for i := start; i >= min; i-- {
// chunk不需要回收内存(已使用页数>=496个页)
if !s.chunks[i].load().shouldScavenge(gen, force) {
continue
}
// 第一个
if i == start {
// i, 位于512位bitmap的位置
return i, chunkPageIndex(searchAddr)
}

// =searchAddr+4MB-8KB
newSearchAddr := chunkBase(i) + pallocChunkBytes - pageSize
//
if marked {
// 替换为newSearchAddr(负数->正数)
cursor.StoreUnmark(searchAddr, newSearchAddr)
} else {
// 替换为newSearchAddr(正数->正数)
cursor.StoreMin(newSearchAddr)
}
// i,511
return i, pallocChunkPages - 1
}

// searchAddrBg或searchAddrForce设为0
cursor.Clear()
return 0, 0
}

// 更新指定chunk元信息,如inUse、gen、scavChunkFlags等。防止scavenger错误回收已分配的页
func (s *scavengeIndex) alloc(ci chunkIdx, npages uint) {
// 读取scavChunkData
sc := s.chunks[ci].load()
// 更新元信息,如inUse、gen、scavChunkFlags等
sc.alloc(npages, s.gen)
// 回写
s.chunks[ci].store(sc)
}

// 更新指定chunk元信息,如inUse、gen、scavChunkFlags等
func (s *scavengeIndex) free(ci chunkIdx, page, npages uint) {
// chunk
sc := s.chunks[ci].load()
// 更新元信息,如inUse、gen、scavChunkFlags等
sc.free(npages, s.gen)
// 回写
s.chunks[ci].store(sc)

// 更新freeHWM
// =chunk起始地址+(页索引+n-1)*8192
addr := chunkBase(ci) + uintptr(page+npages-1)*pageSize
if s.freeHWM.lessThan(offAddr{addr}) {
s.freeHWM = offAddr{addr}
}

// 下面负数表示marked,绝对值为地址
// 更新searchAddrForce
searchAddr, _ := s.searchAddrForce.Load()
if (offAddr{searchAddr}).lessThan(offAddr{addr}) {
// marked表示有可回收的页
s.searchAddrForce.StoreMarked(addr)
}
}

// 更新scavengeIndex版本计数器、searchAddr、freeHWM
func (s *scavengeIndex) nextGen() {
// 版本计数器+=1
s.gen++

// 下面负数表示marked,绝对值为地址
// searchAddrBg=max(searchAddrBg,freeHWM)
searchAddr, _ := s.searchAddrBg.Load()
if (offAddr{searchAddr}).lessThan(s.freeHWM) {
// marked表示有可回收的页
s.searchAddrBg.StoreMarked(s.freeHWM.addr())
}
// 基地址 0xffff800000000000
s.freeHWM = minOffAddr
}

// 更新元信息,如inUse、gen、scavChunkFlags等
func (sc *scavChunkData) alloc(npages uint, newGen uint32) {
// 超过512个页
if uint(sc.inUse)+npages > pallocChunkPages {
print("runtime: inUse=", sc.inUse, " npages=", npages, "\n")
throw("too many pages allocated in chunk?")
}
// 版本不一致
if sc.gen != newGen {
// 纪录历史版本
sc.lastInUse = sc.inUse
// 同步
sc.gen = newGen
}

//
sc.inUse += uint16(npages)
// ==512
if sc.inUse == pallocChunkPages {
// 清除scavChunkHasFree标志
sc.setEmpty()
}
}

// 更新指定chunk元信息,如inUse、gen、scavChunkFlags等
func (sc *scavChunkData) free(npages uint, newGen uint32) {
// 相减会小于0
if uint(sc.inUse) < npages {
print("runtime: inUse=", sc.inUse, " npages=", npages, "\n")
throw("allocated pages below zero?")
}
// 更新
if sc.gen != newGen {
// 纪录上一个inUse
sc.lastInUse = sc.inUse
// 更新版本
sc.gen = newGen
}
// 回收n页
sc.inUse -= uint16(npages)
// 设置scavChunkHasFree标志
sc.setNonEmpty()
}

// chunk是否需要回收内存(已使用页数<496个页)
func (sc scavChunkData) shouldScavenge(currGen uint32, force bool) bool {
// 没有scavChunkHasFree标志
if sc.isEmpty() {
return false
}

// 强制性
if force {
return true
}

// 版本一致
if sc.gen == currGen {
// <496
return sc.inUse < scavChunkHiOccPages && sc.lastInUse < scavChunkHiOccPages
}

// 版本不一致
// <496
return sc.inUse < scavChunkHiOccPages
}

内存管理相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
// 从0到272获取mcentral,再把所有未清扫的mspan一个个返回
func (h *mheap) nextSpanForSweep() *mspan {
//
sg := h.sweepgen
// centralIndex < 272
for sc := sweep.centralIndex.load(); sc < numSweepClasses; sc++ {
// spc = sweepClass>>1; full = sweepClass&1==0
spc, full := sc.split()
// 获取mcentral
c := &h.central[spc].mcentral

var s *mspan
if full {
// full类型
// 从full[1-sweepgen/2%2]尾部获取一个mspan
s = c.fullUnswept(sg).pop()
} else {
// partial类型
// 从partial[1-sweepgen/2%2]尾部获取一个mspan
s = c.partialUnswept(sg).pop()
}

// 获取mspan成功
if s != nil {
// func (s *sweepClass) update(sNew sweepClass)
// 用sc替换centralIndex(前提:sc < centralIndex)
sweep.centralIndex.update(sc)
return s
}
}

// 到这里,没有找到任何mspan

// func (s *sweepClass) update(sNew sweepClass)
// 32位全设置为1
sweep.centralIndex.update(sweepClassDone)
return nil
}

// 用sNew替换s
func (s *sweepClass) update(sNew sweepClass) {
// 旧值
sOld := s.load()
// 只有旧值比新值小才替换
for sOld < sNew && !atomic.Cas((*uint32)(s), uint32(sOld), uint32(sNew)) {
sOld = s.load()
}
}

// 更新pageSpecials
func spanHasNoSpecials(s *mspan) {
// arena内第几个页
arenaPage := (s.base() / pageSize) % pagesPerArena
// arena索引
ai := arenaIndex(s.base())
// 找到heapArena
ha := mheap_.arenas[ai.l1()][ai.l2()]
//
atomic.And8(&ha.pageSpecials[arenaPage/8], ^(uint8(1) << (arenaPage % 8)))
}

// 通过gcmarkBits计算已用对象数
func (s *mspan) countAlloc() int {
count := 0
// 按8的倍数向上取整
bytes := divRoundUp(uintptr(s.nelems), 8)
//
for i := uintptr(0); i < bytes; i += 8 {
mrkBits := *(*uint64)(unsafe.Pointer(s.gcmarkBits.bytep(i)))
// 不区分大端/小端,只计算有多少个1
count += sys.OnesCount64(mrkBits)
}
return count
}

// 返回对象的起始地址及bitmap信息、对象的类型信息(大对象)
func (span *mspan) typePointersOfType(typ *abi.Type, addr uintptr) typePointers {
// 忽略
const doubleCheck = false
if doubleCheck && typ == nil {
throw("bad type passed to typePointersOfType")
}

// noscan类型
if span.spanclass.noscan() {
return typePointers{}
}

// 获取数据类型的GCMask
gcmask := getGCMask(typ)
// 这里应该是大对象才有
return typePointers{elem: addr, addr: addr, mask: readUintptr(gcmask), typ: typ}
}

// 返回对象的起始地址及bitmap信息、对象的类型信息(小对象/对象内部)
func (span *mspan) typePointersOf(addr, size uintptr) typePointers {
// 从mspan内地址转全局地址
base := span.objBase(addr)
// 返回对象的起始地址及bitmap信息、对象的类型信息
tp := span.typePointersOfUnchecked(base)
if base == addr && size == span.elemsize {
return tp
}
// 说明是在对象内部,需要重新定位
// 地址往后移动n字节,获取实际对象地址和bitmap
return tp.fastForward(addr-tp.addr, addr+size)
}

// 返回对象的起始地址及bitmap信息、对象的类型信息
func (span *mspan) typePointersOfUnchecked(addr uintptr) typePointers {
const doubleCheck = false
// 忽略
if doubleCheck && span.objBase(addr) != addr {
print("runtime: addr=", addr, " base=", span.objBase(addr), "\n")
throw("typePointersOfUnchecked consisting of non-base-address for object")
}

spc := span.spanclass

// noscan => scalar没有bitmap信息(这里只判断小对象)
if spc.noscan() {
return typePointers{}
}

// 下面是scan类型或大对象

// 对象大小 <=512B
if heapBitsInSpan(span.elemsize) {
// bitmap共64~128个bit
return typePointers{elem: addr, addr: addr, mask: span.heapBitsSmallForAddr(addr)}
}

// 对象大小 >512B

var typ *_type
if spc.sizeclass() != 0 {
// 小对象
// 头8个字节为header
typ = *(**_type)(unsafe.Pointer(addr))
// 起始地址往后移动8个字节
addr += mallocHeaderSize
} else {
// 大对象
// type在mspan.largeType
typ = span.largeType
if typ == nil {
// noscan为nil,或者因为延迟扫描时,暂时不扫描
return typePointers{}
}
}

// type Example struct {
// a *int8 // 8B (指针)
// b int16 // 2B (非指针,编译器会对齐b为8字节)
// c *int32 // 8B (指针)
// d float64 // 8B (非指针)
// }

// 获取数据类型的GCMask,如上为0b0101
gcmask := getGCMask(typ)
// 大对象才有typ字段信息
return typePointers{elem: addr, addr: addr, mask: readUintptr(gcmask), typ: typ}
}

// 将mask第一个uint64内的第一个指针的位置为0,返回修改后的tp和指针地址
func (tp typePointers) nextFast() (typePointers, uintptr) {
if tp.mask == 0 {
return tp, 0
}

var i int
if goarch.PtrSize == 8 {
// 二进制数尾部0个数
i = sys.TrailingZeros64(uint64(tp.mask))
} else {
i = sys.TrailingZeros32(uint32(tp.mask))
}

// mask内第i位置为0,表示这个指针已经被扫描
tp.mask ^= uintptr(1) << (i & (ptrBits - 1))
// tp,指针地址
return tp, tp.addr + uintptr(i)*goarch.PtrSize
}

// mask第一个uint64不为0则调用nextFast,否则移动下一个uint64再重新判断
func (tp typePointers) next(limit uintptr) (typePointers, uintptr) {
for {
// mask不为0
if tp.mask != 0 {
// 将mask第一个uint64内的第一个指针的位置为0,返回修改后的tp和指针地址
return tp.nextFast()
}

// 下面mask为0

// 没有类型信息-对象<512B或scalar或延迟设置
if tp.typ == nil {
return typePointers{}, 0
}

// 对象>512B

// addr+8*64 > PtrBytes => 表示后面的数据非指针,不用扫描
if tp.addr+goarch.PtrSize*ptrBits >= tp.elem+tp.typ.PtrBytes {
// 指向下一个对象
tp.elem += tp.typ.Size_
tp.addr = tp.elem
} else {
// 对象内部扫描

// 移动512字节
tp.addr += ptrBits * goarch.PtrSize
}

// 越界
if tp.addr >= limit {
return typePointers{}, 0
}

// 移动到addr所在到bitmap字节
tp.mask = readUintptr(addb(getGCMask(tp.typ), (tp.addr-tp.elem)/goarch.PtrSize/8))
// addr+8*64 > limit => 越界,丢弃limit后的位
if tp.addr+goarch.PtrSize*ptrBits > limit {
// 多余的位
bits := (tp.addr + goarch.PtrSize*ptrBits - limit) / goarch.PtrSize
// 移除高bits位
tp.mask &^= ((1 << (bits)) - 1) << (ptrBits - bits)
}
}
}

// 地址往后移动n字节,获取实际对象地址和bitmap
func (tp typePointers) fastForward(n, limit uintptr) typePointers {
// 对象起始地址
target := tp.addr + n
// 越界
if target >= limit {
return typePointers{}
}

// 没有类型信息(从源码看,是小对象<=512B)
if tp.typ == nil {
// =1<<(n/8)-1 => 丢弃target前的位
tp.mask &^= (1 << ((target - tp.addr) / goarch.PtrSize)) - 1
// addr+8*64 > limit => 越界,丢弃limit后的位
if tp.addr+goarch.PtrSize*ptrBits > limit {
// 多余的位
bits := (tp.addr + goarch.PtrSize*ptrBits - limit) / goarch.PtrSize
// 移除高bits位
tp.mask &^= ((1 << (bits)) - 1) << (ptrBits - bits)
}
return tp
}

// 有类型信息(大对象>512B)

// 有多个对象
if n >= tp.typ.Size_ {
// 起始地址
oldelem := tp.elem
// +=n/type_size*type_size => 按type_size的倍数取整,对齐
tp.elem += (tp.addr - tp.elem + n) / tp.typ.Size_ * tp.typ.Size_
// 到这里tp.elem-oldelem <= n

// 实际地址按512的倍数向下取整(tp.addr >= tp.elem) => 实际地址addr可能在对象内部
tp.addr = tp.elem + alignDown(n-(tp.elem-oldelem), ptrBits*goarch.PtrSize)
} else {
// n < type_size => 单个对象内部
// +=n按512的倍数向下取整(tp.elem不变)
tp.addr += alignDown(n, ptrBits*goarch.PtrSize)
}

// 表示addr后面的数据非指针,不用扫描
if tp.addr-tp.elem >= tp.typ.PtrBytes {
// 指向下一个对象
tp.elem += tp.typ.Size_
tp.addr = tp.elem
// 获取数据类型的GCMask
tp.mask = readUintptr(getGCMask(tp.typ))

// 越界
if tp.addr >= limit {
return typePointers{}
}
} else {
// 对象内部扫描

// 移动到addr所在到bitmap字节
tp.mask = readUintptr(addb(getGCMask(tp.typ), (tp.addr-tp.elem)/goarch.PtrSize/8))
// 丢弃target前的位
tp.mask &^= (1 << ((target - tp.addr) / goarch.PtrSize)) - 1
}

// addr+8*64 > limit => 越界,丢弃limit后的位
if tp.addr+goarch.PtrSize*ptrBits > limit {
// 多余的位
bits := (tp.addr + goarch.PtrSize*ptrBits - limit) / goarch.PtrSize
// 移除高bits位
tp.mask &^= ((1 << (bits)) - 1) << (ptrBits - bits)
}
return tp
}

// 合并other链表的mspan数据到list链表前
func (list *mSpanList) takeAll(other *mSpanList) {
// 链表为空
if other.isEmpty() {
return
}

// 所有mspan纪录list指针(debug用)
for s := other.first; s != nil; s = s.next {
s.list = list
}

// list链表为空
if list.isEmpty() {
*list = *other
} else {
// 不为空,other在前,list在后

// 更新next指针
other.last.next = list.first
// 更新prev指针
list.first.prev = other.last
// 更新first指针
list.first = other.first
}

// 链表字段重置
other.first, other.last = nil, nil
}

// 根据地址返回对象的bitmap(64~128个bit)
func (span *mspan) heapBitsSmallForAddr(addr uintptr) uintptr {
// 只有对象大小 <=512B才会访问这个函数,因此需要64个指针的bitmap(1个uint64,最多2个uint64)

// mspan总字节数
spanSize := span.npages * pageSize
// bitmap大小
bitmapSize := spanSize / goarch.PtrSize / 8
// 定位到bitmap起始地址
hbits := (*byte)(unsafe.Pointer(span.base() + spanSize - bitmapSize))

// 第几个uint64
i := (addr - span.base()) / goarch.PtrSize / ptrBits
// 具体bit位置
j := (addr - span.base()) / goarch.PtrSize % ptrBits
// 指针数量
bits := span.elemsize / goarch.PtrSize
// 第1个uint64
word0 := (*uintptr)(unsafe.Pointer(addb(hbits, goarch.PtrSize*(i+0))))
// 第2个uint64
word1 := (*uintptr)(unsafe.Pointer(addb(hbits, goarch.PtrSize*(i+1))))

var read uintptr
if j+bits > ptrBits {
// 需要访问两个uint64
// 第1个uint64要访问的位数量
bits0 := ptrBits - j
// 第2个uint64要访问的位数量
bits1 := bits - bits0
read = *word0 >> j
read |= (*word1 & ((1 << bits1) - 1)) << bits0
} else {
// 单个uint64足以容纳
read = (*word0 >> j) & ((1 << bits) - 1)
}
return read
}

// 重置空的spanSet,清理残留的block
func (b *spanSet) reset() {
// 头部、尾部索引
head, tail := b.index.load().split()
// 不为空
if head < tail {
print("head = ", head, ", tail = ", tail, "\n")
throw("attempt to clear non-empty span set")
}

// spanSet.spine第一维索引 = head/512
top := head / spanSetBlockEntries
if uintptr(top) < b.spineLen.Load() {
// spine数组第二维,spanSetBlock, =spine[top]
blockp := b.spine.Load().lookup(uintptr(top))
block := blockp.Load()
// 第二维有数据
if block != nil {
// 没有执行过pop操作
if block.popped.Load() == 0 {
// 不应该为0
throw("span set block with unpopped elements found in reset")
}
// ==512
if block.popped.Load() == spanSetBlockEntries {
// 也不应该为512,最后一次pop会将指针置为nil
throw("fully empty unfreed span set block found in reset")
}

// Clear the pointer to the block.
blockp.StoreNoWB(nil)

// popped置为0,block放回池子里
spanSetBlockPool.free(block)
}
}
// 置为0
b.index.reset()
// 置为0
b.spineLen.Store(0)
}

// 根据统计数据调整栈的初始大小
func gcComputeStartingStackSize() {
// 默认为1
if debug.adaptivestackstart == 0 {
return
}

var scannedStackSize uint64
var scannedStacks uint64
// 遍历所有p
for _, p := range allp {
scannedStackSize += p.scannedStackSize
scannedStacks += p.scannedStacks

p.scannedStackSize = 0
p.scannedStacks = 0
}

// 为0
if scannedStacks == 0 {
// 设置为2KB
startingStackSize = fixedStack
return
}

// 平均值=x+928(stackGuard确保不会出发扩容)
avg := scannedStackSize/scannedStacks + stackGuard

// 不能超过1GB
if avg > uint64(maxstacksize) {
avg = uint64(maxstacksize)
}
// 不能低于2KB
if avg < fixedStack {
avg = fixedStack
}
// 按2的倍数取整
startingStackSize = uint32(round2(int32(avg)))
}

// mcache清理
func (c *mcache) prepareForSweep() {
// sweepgen
sg := mheap_.sweepgen
// flushGen
flushGen := c.flushGen.Load()
// 相等,返回
if flushGen == sg {
return
} else if flushGen != sg-2 {
println("bad flushGen", flushGen, "in prepareForSweep; sweepgen", sg)
throw("bad flushGen")
}
// alloc列表mspan放到partial或full链表、tiny区域清空
c.releaseAll()
// 清空stackcache,如果是_GCoff阶段,将空的mspan释放回mheap
stackcache_clear(c)
// sweepgen快照,gcStart前同步
c.flushGen.Store(mheap_.sweepgen)
}

// tiny、alloc重置并释放mspan,更新内存统计信息
func (c *mcache) releaseAll() {
// 快照,需要被GC扫描的字节数
scanAlloc := int64(c.scanAlloc)
// 重置
c.scanAlloc = 0

sg := mheap_.sweepgen
// 累计存活字节数
dHeapLive := int64(0)
// 所有mpsan清空
for i := range c.alloc {
// mspan
s := c.alloc[i]
// 不为空
if s != &emptymspan {
// 已分配对象数
slotsUsed := int64(s.allocCount) - int64(s.allocCountBeforeCache)
// 重置
s.allocCountBeforeCache = 0

// statsSeq计数器加1 => 奇数,表示p正在写入stats
// =stats[gen%3] => heapStatsDelta
stats := memstats.heapStats.acquire()
// 累计到全局计数器
atomic.Xadd64(&stats.smallAllocCount[spanClass(i).sizeclass()], slotsUsed)
// statsSeq计数器加1 => 偶数
memstats.heapStats.release()

// 累计已分配字节数
gcController.totalAlloc.Add(slotsUsed * int64(s.elemsize))

// 未过期
if s.sweepgen != sg+1 {
// -=剩余可用字节数
dHeapLive -= int64(s.nelems-s.allocCount) * int64(s.elemsize)
}

// mspan已过期则清理,未过期则根据是否mspan释放有剩余放到partial或full链表
mheap_.central[i].mcentral.uncacheSpan(s)
// 设置为空的mspan
c.alloc[i] = &emptymspan
}
}
// tiny区域清理
c.tiny = 0
c.tinyoffset = 0

// statsSeq计数器加1 => 奇数,表示p正在写入stats
// =stats[gen%3] => heapStatsDelta
stats := memstats.heapStats.acquire()
// 累计到全局tiny计数器
atomic.Xadd64(&stats.tinyAllocCount, int64(c.tinyAllocs))
// 重置
c.tinyAllocs = 0
// statsSeq计数器加1 => 偶数
memstats.heapStats.release()

// 累计heap存活字节数,如果GC未启动则累计heap扫描字节数,否则重新计算辅助GC的工作量转换参数
gcController.update(dHeapLive, scanAlloc)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// 回收指定字节数量的内存,先标记为已分配,释放回系统,最后标记为释放(扫描时是从高地址向低地址进行搜索)
func (p *pageAlloc) scavenge(nbytes uintptr, shouldStop func() bool, force bool) uintptr {
released := uintptr(0)
// 回收的内存字节数没有达到指定值
for released < nbytes {
// 返回可能含有可清理页的chunk的最大索引(扫描时是从高地址向低地址进行搜索)
ci, pageIdx := p.scav.index.find(force)
// 没找到
if ci == 0 {
break
}

// 返回g0执行
systemstack(func() {
// 在指定chunk内回收一定数量的页,先标记为已分配,释放回系统,最后标记为释放
released += p.scavengeOne(ci, pageIdx, nbytes-released)
})

// 如果scavenger需要停止
if shouldStop != nil && shouldStop() {
break
}
}
return released
}

// 在指定chunk内回收一定数量的页,先标记为已分配,释放回系统,最后标记为释放
func (p *pageAlloc) scavengeOne(ci chunkIdx, searchIdx uint, max uintptr) uintptr {
// 页数最大值=max/8192 => 一般是64KB/8KB=8个页
maxPages := max / pageSize
// 有溢出
if max%pageSize != 0 {
maxPages++
}

// 页数最小值=physPageSize/8192
minPages := physPageSize / pageSize
// 最低1个页
if minPages < 1 {
minPages = 1
}

lock(p.mheapLock)
// x = p.summary[4][ci] => pallocSum取max部份 (下面条件一般都符合)
if p.summary[len(p.summary)-1][ci].max() >= uint(minPages) {
// 从pallocData中找到并返回未回收且可用的连续页起始索引以及页数
base, npages := p.chunkOf(ci).findScavengeCandidate(searchIdx, minPages, maxPages)

// 找到n个页
if npages != 0 {
// 起始地址
addr := chunkBase(ci) + uintptr(base)*pageSize

// 先标记为分配,防止其他g获得这片区域
// pallocBits、scavenged更新
p.chunkOf(ci).allocRange(base, npages)
// 统计chunks信息并更新summary
p.update(addr, uintptr(npages), true, true)

// 接下来安全了
unlock(p.mheapLock)

// 非测试
if !p.test {
// 将这片内存释放回系统(Ready->Prepared)
sysUnused(unsafe.Pointer(addr), uintptr(npages)*pageSize)

// 统计数据更新
nbytes := int64(npages * pageSize)
// 累计到heap内存释放量(释放回OS)
gcController.heapReleased.add(nbytes)
// 累计到heap内存可复用量(非free都是减少)
gcController.heapFree.add(-nbytes)

// statsSeq计数器加1 => 奇数,表示p正在写入stats
// =stats[gen%3] => heapStatsDelta
stats := memstats.heapStats.acquire()
atomic.Xaddint64(&stats.committed, -nbytes)
atomic.Xaddint64(&stats.released, nbytes)
// statsSeq计数器加1 => 偶数
memstats.heapStats.release()
}

// 重新加锁
lock(p.mheapLock)
// searchAddr更新
if b := (offAddr{addr}); b.lessThan(p.searchAddr) {
p.searchAddr = b
}
// pallocData.pallocBits更新,指定位置为0
p.chunkOf(ci).free(base, npages)
// 统计chunks信息并更新summary
p.update(addr, uintptr(npages), true, false)

// pallocData.scavenged更新,指定位置为1
p.chunkOf(ci).scavenged.setRange(base, npages)
unlock(p.mheapLock)

// 释放回系统的内存大小
return uintptr(npages) * pageSize
}
}
// 清除scavChunkHasFree标志
p.scav.index.setEmpty(ci)
unlock(p.mheapLock)

return 0
}

// 从pallocData中找到并返回未回收且可用的连续页起始索引以及页数
func (m *pallocData) findScavengeCandidate(searchIdx uint, minimum, max uintptr) (uint, uint) {
// 非2的倍数 or 为0
if minimum&(minimum-1) != 0 || minimum == 0 {
print("runtime: min = ", minimum, "\n")
throw("min must be a non-zero power of 2")
} else if minimum > maxPagesPerPhysPage { // 超过64
print("runtime: min = ", minimum, "\n")
throw("min too large")
}

// minimum -> [1,64]

if max == 0 {
// 最小为minimum
max = minimum
} else {
// 按minimum的倍数取整
max = alignUp(max, minimum)
}

// searchIdx => [0,511]
// i => 64的倍数 => [0,7]
i := int(searchIdx / 64)
// 跳过已使用或已回收的页,从高位往低位扫描
for ; i >= 0; i-- {
// 两个64位数按位或,按minimum个位填充1
// 如fillAligned(0x0100a3, 8) == 0xff00ff => 每8个位有1的全部填充为1
// scavenged中1为已回收,pallocBits中1为已使用,所以,x中0表示未回收且可用
x := fillAligned(m.scavenged[i]|m.pallocBits[i], uint(minimum))
// 存在未回收且可用的数据
if x != ^uint64(0) {
// 退出循环
break
}
}

// 查找失败
if i < 0 {
return 0, 0
}

// 找到未回收且可用的页

// double-check ???
x := fillAligned(m.scavenged[i]|m.pallocBits[i], uint(minimum))
// x取反算出前导0个数(minimum的倍数)
z1 := uint(sys.LeadingZeros64(^x))
// 连续0个数,最后一个0出现的位置
run, end := uint(0), uint(i)*64+(64-z1)
// 丢弃高z1位后不为0
if x<<z1 != 0 {
// 低位有为1的数据
// 丢弃高z1位后前导0的个数
run = uint(sys.LeadingZeros64(x << z1))
} else {
// 丢弃高z1位后为0
// 低位0的个数
run = 64 - z1
// 扩展,继续扫描i之前的位置
for j := i - 1; j >= 0; j-- {
x := fillAligned(m.scavenged[j]|m.pallocBits[j], uint(minimum))
// 高位0个数
run += uint(sys.LeadingZeros64(x))
// 存在已回收或已使用的页
if x != 0 {
// 退出循环
break
}
}
}

// 取最小值
size := min(run, uint(max))
// 0的起始位置
start := end - size

// 确保不会破坏huge page的边界,不讨论
if physHugePageSize > pageSize && physHugePageSize > physPageSize {
pagesPerHugePage := physHugePageSize / pageSize
hugePageAbove := uint(alignUp(uintptr(start), pagesPerHugePage))

if hugePageAbove <= end {
hugePageBelow := uint(alignDown(uintptr(start), pagesPerHugePage))

if hugePageBelow >= end-run {
size = size + (start - hugePageBelow)
start = hugePageBelow
}
}
}
return start, size
}

缓冲区

任务缓冲区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
// 初始化wbuf1、wbuf2
func (w *gcWork) init() {
// 从work.empty链表获取1个空的wbuf
w.wbuf1 = getempty()
// 尝试从work.full获取一个wbuf
wbuf2 := trygetfull()
if wbuf2 == nil {
// 从work.empty链表获取1个空的wbuf
wbuf2 = getempty()
}
w.wbuf2 = wbuf2
}

// 将对象放到wbuf-任务缓冲区,已满则从work.empty获取一个wbuf替换用
func (w *gcWork) put(obj uintptr) {
flushed := false
wbuf := w.wbuf1

// workType、mheap加锁
lockWithRankMayAcquire(&work.wbufSpans.lock, lockRankWbufSpans)
lockWithRankMayAcquire(&mheap_.lock, lockRankMheap)

// wbuf1、wbuf2未初始化
if wbuf == nil {
// 初始化wbuf1、wbuf2
w.init()
// 重新读一遍wbuf1
wbuf = w.wbuf1
} else if wbuf.nobj == len(wbuf.obj) {
// wbuf1已满

// 交换wbuf1、wbuf2
w.wbuf1, w.wbuf2 = w.wbuf2, w.wbuf1
// 重新读一遍wbuf1(实际是wbuf2)
wbuf = w.wbuf1

// wbuf2也满了
if wbuf.nobj == len(wbuf.obj) {
// wbuf2放到work.full
putfull(wbuf)
// 表示还有更多灰色对象需要标记
w.flushedWork = true
// 从work.empty链表获取1个空的wbuf
wbuf = getempty()
// 优先放到wbuf1
w.wbuf1 = wbuf
// 同flushedWork
flushed = true
}
}

// 放到obj数组末尾
wbuf.obj[wbuf.nobj] = obj
// 索引往后移
wbuf.nobj++

// 启动更多标记线程
if flushed && gcphase == _GCmark {
// 如果需要更多的专用标记线程,随机抢占一个p运行
gcController.enlistWorker()
}
}

// 将对象放到wbuf-任务缓冲区,未初始化或已满直接返回
func (w *gcWork) putFast(obj uintptr) bool {
wbuf := w.wbuf1
// wbuf1、wbuf2未初始化 or wbuf1已满
if wbuf == nil || wbuf.nobj == len(wbuf.obj) {
return false
}

// 放到obj数组末尾
wbuf.obj[wbuf.nobj] = obj
// 索引往后移
wbuf.nobj++
return true
}

// 将一批对象放到wbuf-任务缓冲区
func (w *gcWork) putBatch(obj []uintptr) {
// 空数组
if len(obj) == 0 {
return
}

flushed := false
// wbuf1
wbuf := w.wbuf1

// wbuf1、wbuf2未初始化
if wbuf == nil {
// 初始化wbuf1、wbuf2
w.init()
// 重新读一遍wbuf1
wbuf = w.wbuf1
}

// 逐个处理
for len(obj) > 0 {
// wbuf1已满
for wbuf.nobj == len(wbuf.obj) {
// wbuf放到work.full
putfull(wbuf)
// 表示还有更多灰色对象需要标记
w.flushedWork = true
// wbuf2替换wbuf1,从work.empty链表获取1个空的wbuf替换wbuf2
w.wbuf1, w.wbuf2 = w.wbuf2, getempty()
// 重新读一遍wbuf1(实际是wbuf2)
wbuf = w.wbuf1
// 同flushedWork
flushed = true
}
// 复制n个obj
n := copy(wbuf.obj[wbuf.nobj:], obj)
// 索引更新
wbuf.nobj += n
// 剩余量
obj = obj[n:]
}

// 启动更多标记线程
if flushed && gcphase == _GCmark {
// 如果需要更多的专用标记线程,随机抢占一个p运行
gcController.enlistWorker()
}
}

// 从wbuf获取一个obj
func (w *gcWork) tryGet() uintptr {
wbuf := w.wbuf1
// wbuf1、wbuf2未初始化
if wbuf == nil {
// 初始化wbuf1、wbuf2
w.init()
// 重新读一遍wbuf1
wbuf = w.wbuf1
}

// 数组为空
if wbuf.nobj == 0 {
// 交换wbuf1、wbuf2
w.wbuf1, w.wbuf2 = w.wbuf2, w.wbuf1
// 重新读一遍wbuf1(实际是wbuf2)
wbuf = w.wbuf1

// 仍然为空
if wbuf.nobj == 0 {
// 旧
owbuf := wbuf
// 新
// 尝试从work.full获取一个wbuf
wbuf = trygetfull()
// 没有数据,返回
if wbuf == nil {
return 0
}
// wbuf放到work.empty
putempty(owbuf)
//
w.wbuf1 = wbuf
}
}

// 索引往前移
wbuf.nobj--
// 返回数据
return wbuf.obj[wbuf.nobj]
}

// 从wbuf获取一个obj,未初始化或为空直接返回
func (w *gcWork) tryGetFast() uintptr {
wbuf := w.wbuf1
// wbuf1、wbuf2未初始化 or wbuf1为空
if wbuf == nil || wbuf.nobj == 0 {
return 0
}

// 索引往前移
wbuf.nobj--
// 返回数据
return wbuf.obj[wbuf.nobj]
}

// wbuf1、wbuf2根据容量选择放入work.empty或work.full队列
func (w *gcWork) dispose() {
// wbuf1已初始化
if wbuf := w.wbuf1; wbuf != nil {
// 为空
if wbuf.nobj == 0 {
// wbuf放到work.empty
putempty(wbuf)
} else {
// 不为空
// wbuf放到work.full
putfull(wbuf)
// 表示还有更多灰色对象需要标记
w.flushedWork = true
}
// 置为nil
w.wbuf1 = nil

// wbuf2
wbuf = w.wbuf2
// 为空
if wbuf.nobj == 0 {
// wbuf放到work.empty
putempty(wbuf)
} else {
// 不为空
// wbuf放到work.full
putfull(wbuf)
// 表示还有更多灰色对象需要标记
w.flushedWork = true
}
// 置为nil
w.wbuf2 = nil
}


if w.bytesMarked != 0 {
// 加到work.bytesMarked
atomic.Xadd64(&work.bytesMarked, int64(w.bytesMarked))
// 清0
w.bytesMarked = 0
}

if w.heapScanWork != 0 {
// 加到gcController.heapScanWork
gcController.heapScanWork.Add(w.heapScanWork)
// 清0
w.heapScanWork = 0
}
}

// wbuf2不为空则全部放入work.full,否则将wbuf1的一半放到work.full
func (w *gcWork) balance() {
// 未初始化
if w.wbuf1 == nil {
return
}

// wbuf2不为空
if wbuf := w.wbuf2; wbuf.nobj != 0 {
// wbuf2放到work.full
putfull(wbuf)
// 表示还有更多灰色对象需要标记
w.flushedWork = true
// 从work.empty链表获取1个空的wbuf
w.wbuf2 = getempty()
} else if wbuf := w.wbuf1; wbuf.nobj > 4 {
// wbuf1数量>4
// 从wbuf拿走末尾一半数据创建新的wbuf,剩下的放到work.full
w.wbuf1 = handoff(wbuf)
// 表示还有更多灰色对象需要标记
w.flushedWork = true
} else {
// wbuf1为空或对象数量不足4
return
}

// 标记阶段
if gcphase == _GCmark {
// 如果需要更多的专用标记线程,随机抢占一个p运行
gcController.enlistWorker()
}
}

// 从work.empty链表获取1个空的wbuf
func getempty() *workbuf {
var b *workbuf
// work.empty有数据
if work.empty != 0 {
// 获取work.empty第1个
b = (*workbuf)(work.empty.pop())
// 有数据
if b != nil {
// 确保wbuf为空
b.checkempty()
}
}

// workType、mheap加锁
lockWithRankMayAcquire(&work.wbufSpans.lock, lockRankWbufSpans)
lockWithRankMayAcquire(&mheap_.lock, lockRankMheap)

// 没有从empty拿到数据
if b == nil {
var s *mspan

// free链表有数据
if work.wbufSpans.free.first != nil {
//
lock(&work.wbufSpans.lock)
// free链表第一个
s = work.wbufSpans.free.first
// double-check
if s != nil {
// mspan从free迁移到busy
work.wbufSpans.free.remove(s)
work.wbufSpans.busy.insert(s)
}
//
unlock(&work.wbufSpans.lock)
}

// 没有从free链表拿到数据
if s == nil {
systemstack(func() {
// 获取mspan、分配n个页面、更新元信息
// 手动分配4个页,type为spanAllocWorkBuf-3
s = mheap_.allocManual(workbufAlloc/pageSize, spanAllocWorkBuf)
})

// 内存不足
if s == nil {
throw("out of memory")
}

//
lock(&work.wbufSpans.lock)
// mspan放到busy
work.wbufSpans.busy.insert(s)
//
unlock(&work.wbufSpans.lock)
}

// i从0开始,步进为2048,边界为32768
for i := uintptr(0); i+_WorkbufSize <= workbufAlloc; i += _WorkbufSize {
// 分配2KB内存创建workbuf
newb := (*workbuf)(unsafe.Pointer(s.base() + i))
// 初始化
newb.nobj = 0
// 校验地址,如果node地址非法,则抛出异常
lfnodeValidate(&newb.node)
// 第1个wbuf
if i == 0 {
// 直接使用
b = newb
} else {
// 剩余的wbuf
// wbuf放到work.empty
putempty(newb)
}
}
}
return b
}

// wbuf放到work.empty
func putempty(b *workbuf) {
// 确保wbuf为空
b.checkempty()
// 放到work.empty
work.empty.push(&b.node)
}

// wbuf放到work.full
func putfull(b *workbuf) {
// 确保wbuf不为空
b.checknonempty()
// 放到work.full
work.full.push(&b.node)
}

// 尝试从work.full获取一个wbuf
func trygetfull() *workbuf {
// 获取work.full第1个
b := (*workbuf)(work.full.pop())
// 有数据
if b != nil {
// 确保wbuf不为空
b.checknonempty()
return b
}
return b
}

// 从wbuf拿走末尾一半数据创建新的wbuf,剩下的放到work.full
func handoff(b *workbuf) *workbuf {
// 从work.empty链表获取1个空的wbuf
b1 := getempty()
// 拿走一半
n := b.nobj / 2
b.nobj -= n
b1.nobj = n
// 复制数据
memmove(unsafe.Pointer(&b1.obj[0]), unsafe.Pointer(&b.obj[b.nobj]), uintptr(n)*unsafe.Sizeof(b1.obj[0]))

// wbuf放到work.full
putfull(b)
return b1
}

// 清空empty链表,将busy链表数据搬到free链表
func prepareFreeWorkbufs() {
//
lock(&work.wbufSpans.lock)
// work.full链表不为空
if work.full != 0 {
throw("cannot free workbufs when work.full != 0")
}

// wbuf都在empty链表,不关心其mspan归属,直接清空链表,把所有mspan搬到free链表
work.empty = 0
// 合并busy链表的mspan数据到free链表前
work.wbufSpans.free.takeAll(&work.wbufSpans.busy)
//
unlock(&work.wbufSpans.lock)
}

// wbuf的free链表清理64个mspan、mspan内存释放回mheap
func freeSomeWbufs(preemptible bool) bool {
// 一次性只处理64个mspan,每个mspan耗时~1–2 µs
const batchSize = 64

lock(&work.wbufSpans.lock)
// 非_GCoff阶段 or free链表为空
if gcphase != _GCoff || work.wbufSpans.free.isEmpty() {
unlock(&work.wbufSpans.lock)
return false
}

systemstack(func() {
// g
gp := getg().m.curg
// 释放64个mspan,如果遇到抢占则提前退出循环
for i := 0; i < batchSize && !(preemptible && gp.preempt); i++ {
// free链表第一个
span := work.wbufSpans.free.first
// 没数据
if span == nil {
break
}
// 移除指定mspan
work.wbufSpans.free.remove(span)
// 释放页和mspan,type为spanAllocWorkBuf-3
mheap_.freeManual(span, spanAllocWorkBuf)
}
})
// free链表是否还有数据
more := !work.wbufSpans.free.isEmpty()
unlock(&work.wbufSpans.lock)
return more
}

写屏障缓冲区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// 重置wbBuf的next、end指针
func (b *wbBuf) reset() {
// 起始地址
start := uintptr(unsafe.Pointer(&b.buf[0]))
// 重置next
b.next = start

// 默认为false,忽略
if testSmallBuf {
// = &b.buf[9]
b.end = uintptr(unsafe.Pointer(&b.buf[wbMaxEntriesPerCall+1]))
} else {
// 边界,指向最后一个元素的末尾
b.end = start + uintptr(len(b.buf))*unsafe.Sizeof(b.buf[0])
}

// 没有对齐
if (b.end-b.next)%unsafe.Sizeof(b.buf[0]) != 0 {
throw("bad write barrier buffer bounds")
}
}

// 获取写屏障缓冲区可写入位置(容量为1个指针)
func (b *wbBuf) get1() *[1]uintptr {
// 越界,缓冲区已满
if b.next+goarch.PtrSize > b.end {
// 将符合条件的写屏障wbBuf数据放到wbuf,清空wbBuf
wbBufFlush()
}
// p指向next位置
p := (*[1]uintptr)(unsafe.Pointer(b.next))
// 指向下一个可写入位置
b.next += goarch.PtrSize
// 返回缓冲区指针
return p
}

// 获取写屏障缓冲区可写入位置(容量为2个指针)
func (b *wbBuf) get2() *[2]uintptr {
// 越界,缓冲区已满
if b.next+2*goarch.PtrSize > b.end {
// 将符合条件的写屏障wbBuf数据放到wbuf,清空wbBuf
wbBufFlush()
}
// p指向next位置
p := (*[2]uintptr)(unsafe.Pointer(b.next))
// 指向下一个可写入位置
b.next += 2 * goarch.PtrSize
// 返回缓冲区指针
return p
}

// 将符合条件的写屏障wbBuf数据放到wbuf,清空wbBuf
func wbBufFlush() {
// 发生panic
if getg().m.dying > 0 {
// wbBuf的next字段指向buf数组开头
getg().m.p.ptr().wbBuf.discard()
return
}

// 切换到g0执行
systemstack(func() {
// 将符合条件的写屏障wbBuf数据放到wbuf,清空wbBuf
wbBufFlush1(getg().m.p.ptr())
})
}

// 将符合条件的写屏障wbBuf数据放到wbuf,清空wbBuf
func wbBufFlush1(pp *p) {
// 起始地址
start := uintptr(unsafe.Pointer(&pp.wbBuf.buf[0]))
// 总数量
n := (pp.wbBuf.next - start) / unsafe.Sizeof(pp.wbBuf.buf[0])
// 获取所有指针
ptrs := pp.wbBuf.buf[:n]

// 设置为0,防止有新的数据入队
pp.wbBuf.next = 0

// debug.debug.gccheckmark默认为0,忽略
if useCheckmark {
// 遍历wbBuf
for _, ptr := range ptrs {
// 根据指针获取对象起始地址、mspan、offset,设置gcmarkBits,放入wbuf队列
shade(ptr)
}
// 重置wbBuf的next、end指针
pp.wbBuf.reset()
return
}

// gcw是wbuf
gcw := &pp.gcw
pos := 0
// 遍历wbBuf-写屏障缓冲
for _, ptr := range ptrs {
// <4096 => 比合法指针还要小
if ptr < minLegalPointer {
continue
}

// 根据p查找mspan,找到了返回对象在mspan内的位置和对象起始地址
obj, span, objIndex := findObject(ptr, 0, 0)
// 在mspan开头
if obj == 0 {
continue // ???
}

// 获取该对象的bitmap(从gcmarkBits获取,第几个字节、字节内第几位)
mbits := span.markBitsForIndex(objIndex)
// 该位已经设置为1
if mbits.isMarked() {
// 已设置,不处理
continue
}
// 将该位设置为1(只要用mask跟gcmarkBits按位或)
mbits.setMarked()

// 根据mspan起始地址获取heapArena、页索引、页bitmap位置
arena, pageIdx, pageMask := pageIndexOf(span.base())
// 将这个页的bitmap设置为1(粗略,只设置一页,不是所有页,此外,pageMarks在GC启动时清0)
if arena.pageMarks[pageIdx]&pageMask == 0 {
atomic.Or8(&arena.pageMarks[pageIdx], pageMask)
}

// 对象不包含指针
if span.spanclass.noscan() {
// 对象大小累计到bytesMarked-被标记的字节总数
gcw.bytesMarked += uint64(span.elemsize)
continue
}
// 放回写屏障缓冲
ptrs[pos] = obj
// 索引调整
pos++
}

// 将一批对象放到wbuf-任务缓冲区
gcw.putBatch(ptrs[:pos])

// 重置wbBuf的next、end指针
pp.wbBuf.reset()
}

gcControllerState-垃圾回收节奏控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
// 设置GC启动阈值、计算目标heap大小和跑道大小,计算辅助GC的工作量转换参数,计算GC触发阈值和目标heap大小,更新sweeper、scavenger的pacing参数
func gcControllerCommit() {
// 空函数(staticlockranking默认为false)
assertWorldStoppedOrLockHeld(&mheap_.lock)

// 设置GC启动阈值、计算目标heap大小和跑道大小
gcController.commit(isSweepDone())

// GC运行中
if gcphase != _GCoff {
// 计算辅助GC的工作量转换参数
gcController.revise()
}

// 计算GC触发阈值和目标heap大小
trigger, heapGoal := gcController.trigger()
// 更新sweeper的pacing参数
gcPaceSweeper(trigger)
// 更新scavenger的pacing参数
gcPaceScavenger(gcController.memoryLimit.Load(), heapGoal, gcController.lastHeapGoal)
}

// 更新sweeper的pacing参数
// 标记终止阶段调用,或GCPercent/MemoryLimit有变动时调用
func gcPaceSweeper(trigger uint64) {
// 空函数(staticlockranking默认为false)
assertWorldStoppedOrLockHeld(&mheap_.lock)

// sweeper数量为0
if isSweepDone() {
// 重置
mheap_.sweepPagesPerByte = 0
} else {
// sweeper数量不为0 => 还在清扫中

// heap存活字节数
heapLiveBasis := gcController.heapLive.Load()
// 距离GC触发的剩余堆大小=触发临界点-heap存活字节数
heapDistance := int64(trigger) - int64(heapLiveBasis)

// 增加边距,防止有页未被清理
heapDistance -= 1024 * 1024
// 最低为8192
if heapDistance < _PageSize {
heapDistance = _PageSize
}

// 已完成清扫的页数量
pagesSwept := mheap_.pagesSwept.Load()
// heap内存页使用量(mSpanInUse)
pagesInUse := mheap_.pagesInUse.Load()
// 剩余待清扫页数量
sweepDistancePages := int64(pagesInUse) - int64(pagesSwept)
if sweepDistancePages <= 0 {
// 不能为负数,重置为0
mheap_.sweepPagesPerByte = 0
} else {
// 还没清扫完
// 每分配1字节需要清扫的页数=剩余待清扫页数量/距离GC触发的剩余堆大小
mheap_.sweepPagesPerByte = float64(sweepDistancePages) / float64(heapDistance)
// 纪录heapLive快照
mheap_.sweepHeapLiveBasis = heapLiveBasis
// 纪录pagesSwept快照-已完成清扫的页数量
mheap_.pagesSweptBasis.Store(pagesSwept)
}
}
}

// 更新scavenger的pacing参数
func gcPaceScavenger(memoryLimit int64, heapGoal, lastHeapGoal uint64) {
// 空函数(staticlockranking默认为false)
assertWorldStoppedOrLockHeld(&mheap_.lock)

// memoryLimit*0.95
memoryLimitGoal := uint64(float64(memoryLimit) * (1 - reduceExtraPercent/100.0))

// 已映射且可用的内存量(总内存)
mappedReady := gcController.mappedReady.Load()

// 总内存没有超过限制
if mappedReady <= memoryLimitGoal {
// 设置为最大值=2^64-1
scavenge.memoryLimitGoal.Store(^uint64(0))
} else {
// 总内存超过限制
// 设置为当前值
scavenge.memoryLimitGoal.Store(memoryLimitGoal)
}

// 第一次GC还没完成,没有足够的信息,停止清理
if lastHeapGoal == 0 {
scavenge.gcPercentGoal.Store(^uint64(0))
return
}
// 计算scavenging goal
goalRatio := float64(heapGoal) / float64(lastHeapGoal)
gcPercentGoal := uint64(float64(memstats.lastHeapInUse) * goalRatio)
// += gcPercentGoal/10 => 这里的retainExtraPercent作用是为了得到更好的被除数
gcPercentGoal += gcPercentGoal / (1.0 / (retainExtraPercent / 100.0))
// 按物理页的大小对齐
gcPercentGoal = (gcPercentGoal + uint64(physPageSize) - 1) &^ (uint64(physPageSize) - 1)

// =heapInUse+heapFree(gcController)
heapRetainedNow := heapRetained()

// 如果小于等于goal or 差值在一个物理页内
if heapRetainedNow <= gcPercentGoal || heapRetainedNow-gcPercentGoal < uint64(physPageSize) {
// 停止清理
scavenge.gcPercentGoal.Store(^uint64(0))
} else {
//
scavenge.gcPercentGoal.Store(gcPercentGoal)
}
}

// 初始化
func (c *gcControllerState) init(gcPercent int32, memoryLimit int64) {
// heap最小为4MB
c.heapMinimum = defaultHeapMinimum
// ^uint64(0) => 64位全为1
c.triggered = ^uint64(0)
// 设置gcPercent并返回旧值(gcPercent从GOGC环境变量获取,默认为100)
c.setGCPercent(gcPercent)
// 设置memoryLimit(memoryLimit从GOMEMLIMIT环境变量获取,默认2^64-1)
c.setMemoryLimit(memoryLimit)
// 设置GC启动阈值、计算目标heap大小和跑道大小
c.commit(true)
}

// 设置gcPercent并返回旧值
func (c *gcControllerState) setGCPercent(in int32) int32 {
// 测试,忽略
if !c.test {
assertWorldStoppedOrLockHeld(&mheap_.lock)
}

// 当前值
out := c.gcPercent.Load()
// 最小为-1
if in < 0 {
in = -1
}
// =4MB*GCPercent/100 => GCPercent默认100
c.heapMinimum = defaultHeapMinimum * uint64(in) / 100
// 纪录GCPercent
c.gcPercent.Store(in)

return out
}

// 设置GC启动阈值、计算目标heap大小和跑道大小
func (c *gcControllerState) commit(isSweepDone bool) {
// 测试,忽略
if !c.test {
// 空函数(staticlockranking默认为false)
assertWorldStoppedOrLockHeld(&mheap_.lock)
}

if isSweepDone {
// sweeper数量为0 => 清扫阶段已结束,可以随时启动GC
// =0
c.sweepDistMinTrigger.Store(0)
} else {
// sweeper数量不为0 => 还在清扫,设置一个阈值,避免GC过早启动
// =heapLive+1MB
c.sweepDistMinTrigger.Store(c.heapLive.Load() + sweepMinHeapDistance)
}

// 计算目标heap大小,可以粗略地认为是2倍heap大小

// 初始值设为64位数最大值
gcPercentHeapGoal := ^uint64(0)
// gcPercent默认为100
if gcPercent := c.gcPercent.Load(); gcPercent >= 0 {
//目标heap大小 =上一次GC后heap存活字节数 + (上一次GC后stack扫描字节数+全部模块的bss+data段大小)*100/100
gcPercentHeapGoal = c.heapMarked + (c.heapMarked+c.lastStackScan.Load()+c.globalsScan.Load())*uint64(gcPercent)/100
}

// 最低为4MB
if gcPercentHeapGoal < c.heapMinimum {
gcPercentHeapGoal = c.heapMinimum
}
c.gcPercentHeapGoal.Store(gcPercentHeapGoal)

// 计算runway大小,默认情况下为根对象大小的3倍

// =consMark*(1-0.25)/0.25*(heap扫描字节数+stack扫描字节数+全部模块的bss+data段大小)
c.runway.Store(uint64((c.consMark * (1 - gcGoalUtilization) / (gcGoalUtilization)) * float64(c.lastHeapScan+c.lastStackScan.Load()+c.globalsScan.Load())))
}

// 计算GC触发阈值和目标heap大小
func (c *gcControllerState) trigger() (uint64, uint64) {
// 计算目标heap大小(一般情况下是上一次GC后heap存活字节数的两倍)、最小目标heap大小
// goal介于上一次GC后heap存活字节数和2*上一次GC后heap存活字节数
// minTrigger为0或heap存活字节数+1MB
goal, minTrigger := c.heapGoalInternal()

// 上一次GC后heap存活字节数 >= 目标heap大小
if c.heapMarked >= goal {
// goal不应该小于heapMarked
return goal, goal
}

// c.heapMarked < goal

// =max(最小目标heap大小,上一次GC后heap存活字节数)
if minTrigger < c.heapMarked {
minTrigger = c.heapMarked
}

// 最低为70%触发
// =(目标heap大小-上一次GC后heap存活字节数)/64*45 + 上一次GC后heap存活字节数
triggerLowerBound := ((goal-c.heapMarked)/triggerRatioDen)*minTriggerRatioNum + c.heapMarked
if minTrigger < triggerLowerBound {
minTrigger = triggerLowerBound
}

// 最高为95%触发
// =(目标heap大小-上一次GC后heap存活字节数)/64*61 + 上一次GC后heap存活字节数
maxTrigger := ((goal-c.heapMarked)/triggerRatioDen)*maxTriggerRatioNum + c.heapMarked
// 目标heap大小>4MB and 目标heap大小-4MB>maxTrigger
if goal > defaultHeapMinimum && goal-defaultHeapMinimum > maxTrigger {
// =目标heap大小-4MB
maxTrigger = goal - defaultHeapMinimum
}
maxTrigger = max(maxTrigger, minTrigger)

// 触发阈值
var trigger uint64

// 飞机起飞前需要滑行的距离,默认情况下为根对象大小的3倍
runway := c.runway.Load()
if runway > goal {
// 容错空间比目标heap大
// 取最小目标heap大小
trigger = minTrigger
} else {
// 容错空间比目标heap小
// 取剩余量
trigger = goal - runway
}
// 最小为minTrigger
trigger = max(trigger, minTrigger)
// 最大为maxTrigger
trigger = min(trigger, maxTrigger)

// 不可能
if trigger > goal {
print("trigger=", trigger, " heapGoal=", goal, "\n")
print("minTrigger=", minTrigger, " maxTrigger=", maxTrigger, "\n")
throw("produced a trigger greater than the heap goal")
}
return trigger, goal
}

// 重置相关字段,计算不同模式的标记工作线程目标,设置最大标记工作线程数,计算辅助GC的工作量转换参数
func (c *gcControllerState) startCycle(markStartTime int64, procs int, trigger gcTrigger) {
// 重置
c.heapScanWork.Store(0)
c.stackScanWork.Store(0)
c.globalsScanWork.Store(0)
c.bgScanCredit.Store(0)
c.assistTime.Store(0)
c.dedicatedMarkTime.Store(0)
c.fractionalMarkTime.Store(0)
c.idleMarkTime.Store(0)
c.markStartTime = markStartTime // 开始时间
c.triggered = c.heapLive.Load() // heap存活字节数

// GC设置25%的CPU使用率(GOGC=100时)

// 利用率目标 =procs*0.25(假设为6核处理器,则结果为1.5)
totalUtilizationGoal := float64(procs) * gcBackgroundUtilization
// 四舍五入(假设为6核处理器,则结果为2)
dedicatedMarkWorkersNeeded := int64(totalUtilizationGoal + 0.5)
// (假设为6核处理器,则结果为0.333)
utilError := float64(dedicatedMarkWorkersNeeded)/totalUtilizationGoal - 1
const maxUtilError = 0.3
// 分数的绝对值超过0.3
if utilError < -maxUtilError || utilError > maxUtilError {
// 目标值25%,实际超过30%,启动比例标记线程(GOMAXPROCS<=3 or GOMAXPROCS=6时)
if float64(dedicatedMarkWorkersNeeded) > totalUtilizationGoal {
// 减少专用标记线程(假设为6核处理器,则结果为1)
dedicatedMarkWorkersNeeded--
}
// (假设为6核处理器,则结果为=(1.5-1)/6=0.083)
c.fractionalUtilizationGoal = (totalUtilizationGoal - float64(dedicatedMarkWorkersNeeded)) / float64(procs)
} else {
// 分数的绝对值小于等于0.3
// 设为0
c.fractionalUtilizationGoal = 0
}

// gcstoptheworld默认为0,忽略
if debug.gcstoptheworld > 0 {
// STW时,只需要专用标记线程
dedicatedMarkWorkersNeeded = int64(procs)
c.fractionalUtilizationGoal = 0
}

// 遍历所有p
for _, p := range allp {
// 重置时间
p.gcAssistTime = 0
// 比例标记任务耗时
p.gcFractionalMarkTime = 0
}

// 超时触发
if trigger.kind == gcTriggerTime {
// 减少空闲标记线程数量,某些场景下至少有一个专用标记线程
if dedicatedMarkWorkersNeeded > 0 {
// 大于0
// 设置最大空闲标记线程数,不超过2^31
// 如果此时idleMarkWorkers=3,那么这个值就是最大值
c.setMaxIdleMarkWorkers(0)
} else {
// 为0
// 设置最大空闲标记线程数,不超过2^32
c.setMaxIdleMarkWorkers(1)
}
} else {
// heap触发或手动/强制

// 设置最大空闲标记线程数(假设为6核处理器,则结果为5)
c.setMaxIdleMarkWorkers(int32(procs) - int32(dedicatedMarkWorkersNeeded))
}

// 纪录专用标记线程数量
c.dedicatedMarkWorkersNeeded.Store(dedicatedMarkWorkersNeeded)
// 计算辅助GC的工作量转换参数
c.revise()
}

// 设置最大空闲标记线程数
func (c *gcControllerState) setMaxIdleMarkWorkers(max int32) {
for {
old := c.idleMarkWorkers.Load()
n := int32(old & uint64(^uint32(0)))
// 溢出(超过2^31)
if n < 0 {
print("n=", n, " max=", max, "\n")
throw("negative idle mark workers")
}
// 不超过2^31
new := uint64(uint32(n)) | (uint64(max) << 32)
if c.idleMarkWorkers.CompareAndSwap(old, new) {
return
}
}
}

// 计算辅助GC的工作量转换参数
func (c *gcControllerState) revise() {
// GC参数
gcPercent := c.gcPercent.Load()
// GOGC为负数,被禁用,但是用户强制执行GC
if gcPercent < 0 {
gcPercent = 100000
}

// heap相关参数
live := c.heapLive.Load() // heap存活字节数
scan := c.heapScan.Load() // heap扫描字节数 - 待扫描
// 已扫描字节数 =heap扫描字节数+栈扫描字节数+bss/data段扫描字节数
work := c.heapScanWork.Load() + c.stackScanWork.Load() + c.globalsScanWork.Load()

// 计算目标heap大小(一般情况下是上一次GC后heap存活字节数的两倍)
heapGoal := int64(c.heapGoal())

// 预期的扫描工作量 =heap扫描字节数+栈扫描字节数+全部模块的bss+data段大小
scanWorkExpected := int64(c.lastHeapScan + c.lastStackScan.Load() + c.globalsScan.Load())

// =累计所有的栈字节数
maxStackScan := c.maxStackScan.Load()
// 最坏情况下的扫描量 =heap扫描字节数+累计所有的栈字节数+全部模块的bss+data段大小
maxScanWork := int64(scan + maxStackScan + c.globalsScan.Load())

// 调整目标heap大小
if work > scanWorkExpected {
// 软限制
// =(heapGoal-triggered)/scanWorkExpected*maxScanWork+triggered
extHeapGoal := int64(float64(heapGoal-int64(c.triggered))/float64(scanWorkExpected)*float64(maxScanWork)) + int64(c.triggered)
//
scanWorkExpected = maxScanWork

// 硬限制
// 2倍的heapGoal(GOGC=100时)
hardGoal := int64((1.0 + float64(gcPercent)/100.0) * float64(heapGoal))
// 以硬限制为准
if extHeapGoal > hardGoal {
extHeapGoal = hardGoal
}

heapGoal = extHeapGoal
}

// 如果存活对象已经超过目标heap大小
if int64(live) > heapGoal {
// 放宽,允许超出10%
const maxOvershoot = 1.1
heapGoal = int64(float64(heapGoal) * maxOvershoot)

//
scanWorkExpected = maxScanWork
}

// 剩余扫描工作量=预计的总扫描工作量-已完成的扫描工作 => bss+data 或 bss+data+stack
scanWorkRemaining := scanWorkExpected - work
// 最低为1000
if scanWorkRemaining < 1000 {
scanWorkRemaining = 1000
}

// 剩余heap空间 => heapGoal-heap存活字节数
heapRemaining := heapGoal - int64(live)
if heapRemaining <= 0 {
// 不应该发生,为防止除以0,设置为1
heapRemaining = 1
}

// 每分配1字节需要辅助完成多少GC工作量
assistWorkPerByte := float64(scanWorkRemaining) / float64(heapRemaining)
// 每完成1单位GC工作量可以分配多少字节
assistBytesPerWork := float64(heapRemaining) / float64(scanWorkRemaining)
c.assistWorkPerByte.Store(assistWorkPerByte)
c.assistBytesPerWork.Store(assistBytesPerWork)
}

// 计算目标heap大小和并发标记进度
func (c *gcControllerState) endCycle(now int64, procs int, userForced bool) {
// 计算目标heap大小(一般情况下是上一次GC后heap存活字节数的两倍)
gcController.lastHeapGoal = c.heapGoal()

// 标记阶段耗时
assistDuration := now - c.markStartTime

// GC利用率 =0.25
utilization := gcBackgroundUtilization
// 防止除以0
if assistDuration > 0 {
// += 助攻积分/总耗时
utilization += float64(c.assistTime.Load()) / float64(assistDuration*int64(procs))
}

// heap存活字节数 <= GC启动前heap存活字节数
if c.heapLive.Load() <= c.triggered {
// 不应该发生
return
}

// 空闲GC利用率
idleUtilization := 0.0
if assistDuration > 0 {
// =闲标记耗时/总耗时
idleUtilization = float64(c.idleMarkTime.Load()) / float64(assistDuration*int64(procs))
}

// 总扫描字节数 =heap扫描字节数+stack扫描字节数+bss/data扫描字节数
scanWork := c.heapScanWork.Load() + c.stackScanWork.Load() + c.globalsScanWork.Load()

// 当前并发标记进度 =(heap存活字节数-GC启动前heap存活字节数)*(GC利用率+空闲GC利用率)/(总扫描字节数*(1-GC利用率))
currentConsMark := (float64(c.heapLive.Load()-c.triggered) * (utilization + idleUtilization)) /
(float64(scanWork) * (1 - utilization))

// currentConsMark > 1.0 => GC当前落后于内存分配速度,会启用更激进的GC策略
// currentConsMark < 1.0 => GC当前标记进度是健康的

// 上一次并发标记进度
oldConsMark := c.consMark
c.consMark = currentConsMark
// 遍历历史4个consMark,已最大的为准
for i := range c.lastConsMark {
if c.lastConsMark[i] > c.consMark {
c.consMark = c.lastConsMark[i]
}
}
// 丢弃最旧的一个
copy(c.lastConsMark[:], c.lastConsMark[1:])
// 放到第一个位置
c.lastConsMark[len(c.lastConsMark)-1] = currentConsMark
}

// 标记终止时,纪录快照,重置heapLive、heapMarked等字段
func (c *gcControllerState) resetLive(bytesMarked uint64) {
c.heapMarked = bytesMarked
c.heapLive.Store(bytesMarked)
c.heapScan.Store(uint64(c.heapScanWork.Load()))
c.lastHeapScan = uint64(c.heapScanWork.Load())
c.lastStackScan.Store(uint64(c.stackScanWork.Load()))
c.triggered = ^uint64(0)
}

// 计算目标heap大小(一般情况下是上一次GC后heap存活字节数的两倍)
func (c *gcControllerState) heapGoal() uint64 {
// 计算目标heap大小(一般情况下是上一次GC后heap存活字节数的两倍)、最小目标heap大小
goal, _ := c.heapGoalInternal()
return goal
}

// 计算目标heap大小(一般情况下是上一次GC后heap存活字节数的两倍)、最小目标heap大小
func (c *gcControllerState) heapGoalInternal() (goal, minTrigger uint64) {
// 目标heap大小,可以粗略的认为是2*上一次GC后heap存活字节数
goal = c.gcPercentHeapGoal.Load()

// 根据总内存限制计算目标heap大小,最小为上一次GC后heap存活字节数
if newGoal := c.memoryLimitHeapGoal(); newGoal < goal {
goal = newGoal
} else {
// newGoal >= 目标heap大小
// 一般情况下,memoryLimit不设置,所以都会走到这里

// 清扫阶段已结束为0,否则为heap存活字节数+1MB
sweepDistTrigger := c.sweepDistMinTrigger.Load()

// 目标heap大小 =max(goal,sweepDistTrigger)
if sweepDistTrigger > goal {
goal = sweepDistTrigger
}

// 最小目标heap大小
minTrigger = sweepDistTrigger

const minRunway = 64 << 10 // 容错量=64KB
// GC已启动 and 目标heap大小 < 上一次GC后heap存活字节数+64KB
if c.triggered != ^uint64(0) && goal < c.triggered+minRunway {
goal = c.triggered + minRunway
}
}
// goal介于上一次GC后heap存活字节数和2*上一次GC后heap存活字节数
// minTrigger为0或heap存活字节数+1MB
return
}

// 根据总内存限制计算目标heap大小,最小为上一次GC后heap存活字节数
func (c *gcControllerState) memoryLimitHeapGoal() uint64 {
var heapFree, heapAlloc, mappedReady uint64
// 确认当前内存状态一致、合法
for {
heapFree = c.heapFree.load() // heap内存可复用量
heapAlloc = c.totalAlloc.Load() - c.totalFree.Load() // 累计已分配字节数-累计回收对象数量
mappedReady = c.mappedReady.Load() // 已映射且可用的内存量(总内存)
if heapFree+heapAlloc <= mappedReady {
// 一切正常
break
}
// 不可能发生,因为部份更新的缘故,需要重试
}

// 总内存限制 = heap+nonHeap
memoryLimit := uint64(c.memoryLimit.Load())

// 非heap内存 =总内存-heap内存可复用量-累计已分配字节数
nonHeapMemory := mappedReady - heapFree - heapAlloc

var overage uint64
// 总内存超过限制
if mappedReady > memoryLimit {
// 超出部份
overage = mappedReady - memoryLimit
}

// 非heap内存+内存超出部份 >= memoryLimit
if nonHeapMemory+overage >= memoryLimit {
// 上一次GC后heap存活字节数 => 立即回收
return c.heapMarked
}

// 距离内存限制剩余量
goal := memoryLimit - (nonHeapMemory + overage)

// 容错空间 =3%的goal,最低为1MB
headroom := goal / 100 * memoryLimitHeapGoalHeadroomPercent
if headroom < memoryLimitMinHeapGoalHeadroom {
headroom = memoryLimitMinHeapGoalHeadroom
}

// <容错空间 or <2倍的容错空间
if goal < headroom || goal-headroom < headroom {
goal = headroom
} else {
goal = goal - headroom
}

// goal >= headroom

// 最低为上一次GC后heap存活字节数
if goal < c.heapMarked {
goal = c.heapMarked
}
return goal
}

// 累计标记耗时、复原计数器
func (c *gcControllerState) markWorkerStop(mode gcMarkWorkerMode, duration int64) {
switch mode {
case gcMarkWorkerDedicatedMode: // 专用标记任务
// 累计标记耗时
c.dedicatedMarkTime.Add(duration)
// 重置,启动worker时-1,复原时+1
c.dedicatedMarkWorkersNeeded.Add(1)
case gcMarkWorkerFractionalMode: // 比例标记任务
// 累计标记耗时
c.fractionalMarkTime.Add(duration)
case gcMarkWorkerIdleMode: // 空闲标记任务
// 累计标记耗时
c.idleMarkTime.Add(duration)
// idleMarkWorkers计数器减1
c.removeIdleMarkWorker()
default:
throw("markWorkerStop: unknown mark worker mode")
}
}

// 累计heap存活字节数,如果GC未启动则累计heap扫描字节数,否则重新计算辅助GC的工作量转换参数
func (c *gcControllerState) update(dHeapLive, dHeapScan int64) {
if dHeapLive != 0 {
live := gcController.heapLive.Add(dHeapLive)
}

// GC未启动/停止
if gcBlackenEnabled == 0 {
// 需要被GC扫描的字节数不为0 => scan类型
if dHeapScan != 0 {
// 累计到heapScan
gcController.heapScan.Add(dHeapScan)
}
} else {
// 计算辅助GC的工作量转换参数
c.revise()
}
}

// 空闲标记线程数量是否超过最大限制
func (c *gcControllerState) needIdleMarkWorker() bool {
p := c.idleMarkWorkers.Load()
// 低32位,高32位
n, max := int32(p&uint64(^uint32(0))), int32(p>>32)
// 空闲标记线程数量是否超过最大限制
return n < max
}

// 空闲标记线程数量加1,超过最大限制时失败
func (c *gcControllerState) addIdleMarkWorker() bool {
for {
old := c.idleMarkWorkers.Load()
// 低32位,高32位
n, max := int32(old&uint64(^uint32(0))), int32(old>>32)
// 溢出
if n >= max {
return false
}
// 溢出
if n < 0 {
print("n=", n, " max=", max, "\n")
throw("negative idle mark workers")
}
// 计数器加1
new := uint64(uint32(n+1)) | (uint64(max) << 32)
if c.idleMarkWorkers.CompareAndSwap(old, new) {
// 成功
return true
}
// 失败,重试
}
}

// idleMarkWorkers计数器减1
func (c *gcControllerState) removeIdleMarkWorker() {
for {
old := c.idleMarkWorkers.Load()
// 低32位,高32位
n, max := int32(old&uint64(^uint32(0))), int32(old>>32)
// 溢出
if n-1 < 0 {
print("n=", n, " max=", max, "\n")
throw("negative idle mark workers")
}
// 计数器减1
new := uint64(uint32(n-1)) | (uint64(max) << 32)
// 交换
if c.idleMarkWorkers.CompareAndSwap(old, new) {
// 成功
return
}
// 失败,重试
}
}

// 如果需要更多的专用标记线程,随机抢占一个p运行
func (c *gcControllerState) enlistWorker() {
// 已经不需要新的空闲标记线程了
if c.dedicatedMarkWorkersNeeded.Load() <= 0 {
return
}

// 需要更多的专用标记线程,抢占一个p运行

// 只有1个CPU
if gomaxprocs <= 1 {
return
}
gp := getg()
//
if gp == nil || gp.m == nil || gp.m.p == 0 {
return
}
// p的索引
myID := gp.m.p.ptr().id
// 随机挑选一个p抢占
for tries := 0; tries < 5; tries++ {
id := int32(cheaprandn(uint32(gomaxprocs - 1)))
if id >= myID {
id++
}
p := allp[id]
// 运行中,重试
if p.status != _Prunning {
continue
}
// 设置g、p抢占标志、发送抢占信号给m
if preemptone(p) {
return
}
}
}

// 从gcBgMarkWorkerPool获取一个g,符合条件则返回
func (c *gcControllerState) findRunnableGCWorker(pp *p, now int64) (*g, int64) {
// GC未启动/停止
if gcBlackenEnabled == 0 {
throw("gcControllerState.findRunnable: blackening not enabled")
}

// 当前时刻
if now == 0 {
now = nanotime()
}

// 耗时超过10ms则需要更新
if gcCPULimiter.needUpdate(now) {
// 计算mutator耗时跟gc耗时,判断是否需要限制GC运行(加锁)
gcCPULimiter.update(now)
}

// 是否还有标记任务(根对象是否扫描完,任务缓冲区是否为空)
if !gcMarkWorkAvailable(pp) {
// 没有任务可执行
return nil, now
}

// 不需要新的专用标记线程以及比例标记线程
if c.dedicatedMarkWorkersNeeded.Load() <= 0 && c.fractionalUtilizationGoal == 0 {
// 返回nil
return nil, now
}

// 从gcBgMarkWorkerPool拿一个g
node := (*gcBgMarkWorkerNode)(gcBgMarkWorkerPool.pop())
// 没有数据
if node == nil {
return nil, now
}

// v>0时,减1
decIfPositive := func(val *atomic.Int64) bool {
for {
v := val.Load()
if v <= 0 {
return false
}

if val.CompareAndSwap(v, v-1) {
return true
}
}
}

// 启动标记线程,dedicatedMarkWorkersNeeded-=1,线程退出时会加回来
if decIfPositive(&c.dedicatedMarkWorkersNeeded) {
// 成功

// 专用标记任务
pp.gcMarkWorkerMode = gcMarkWorkerDedicatedMode
} else if c.fractionalUtilizationGoal == 0 {
// 比例标记任务利用率目标为0

// 放回池子
gcBgMarkWorkerPool.push(&node.node)
return nil, now
} else {
// 比例标记任务利用率目标不为0

// 标记阶段耗时
delta := now - c.markStartTime
// 比例标记任务下的标记耗时/总耗时 > 比例标记任务利用率目标
if delta > 0 && float64(pp.gcFractionalMarkTime)/float64(delta) > c.fractionalUtilizationGoal {
// 超过比例标记任务利用率目标
// g放回池子
gcBgMarkWorkerPool.push(&node.node)
return nil, now
}
// 没有超过比例标记任务利用率目标
// 启动一个比例标记线程
pp.gcMarkWorkerMode = gcMarkWorkerFractionalMode
}

// g
gp := node.gp.ptr()
// 从_Gwaiting状态改为_Grunnable
casgstatus(gp, _Gwaiting, _Grunnable)

return gp, now
}

gcCPULimiterState-CPU限制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// enabled为true => 限制GC运行
func (l *gcCPULimiterState) limiting() bool {
return l.enabled.Load()
}

// 耗时超过10ms则需要更新
func (l *gcCPULimiterState) needUpdate(now int64) bool {
// now-lastUpdate > 10e6
return now-l.lastUpdate.Load() > gcCPULimiterUpdatePeriod
}

// 同步gcEnabled、标记当前处于过渡状态,计算mutator耗时跟gc耗时,判断是否需要限制GC运行(加锁)
func (l *gcCPULimiterState) startGCTransition(enableGC bool, now int64) {
// 原子更新lock,0->1
if !l.tryLock() {
// 失败
throw("failed to acquire lock to start a GC transition")
}
// 跟传入的参数一样,状态没有发生变更
if l.gcEnabled == enableGC {
throw("transitioning GC to the same state as before?")
}
// 计算mutator耗时跟gc耗时,判断是否需要限制GC运行
l.updateLocked(now)
// 同步
l.gcEnabled = enableGC
// 标记当前处于过渡状态
l.transitioning = true
}

// 解除过渡状态,计算mutator耗时跟gc耗时,判断是否需要限制GC运行(加锁)
func (l *gcCPULimiterState) finishGCTransition(now int64) {
// 状态不对,处于非过渡状态
if !l.transitioning {
throw("finishGCTransition called without starting one?")
}
// 纪录耗时
if lastUpdate := l.lastUpdate.Load(); now >= lastUpdate {
// 根据mutator耗时跟gc耗时判断是否需要限制GC运行
l.accumulate(0, (now-lastUpdate)*int64(l.nprocs))
}
// 同步now
l.lastUpdate.Store(now)
// 重置
l.transitioning = false
// 解锁
l.unlock()
}


// 计算mutator耗时跟gc耗时,判断是否需要限制GC运行(加锁)
func (l *gcCPULimiterState) update(now int64) {
// 原子更新lock,0->1
if !l.tryLock() {
// 失败
return
}

// 异常,处于过渡状态中,已经在调整了
if l.transitioning {
throw("update during transition")
}
// 计算mutator耗时跟gc耗时,判断是否需要限制GC运行
l.updateLocked(now)
// 解锁
l.unlock()
}

// 计算mutator耗时跟gc耗时,判断是否需要限制GC运行
func (l *gcCPULimiterState) updateLocked(now int64) {
// 上一次update时的now参数
lastUpdate := l.lastUpdate.Load()
// 过期不处理
if now < lastUpdate {
return
}

// CPU总耗时
windowTotalTime := (now - lastUpdate) * int64(l.nprocs)
// 纪录now
l.lastUpdate.Store(now)

// 将助攻积分清0
assistTime := l.assistTimePool.Load()
if assistTime != 0 {
l.assistTimePool.Add(-assistTime)
}

// 将p的空闲辅助标记耗时清0
idleTime := l.idleTimePool.Load()
if idleTime != 0 {
l.idleTimePool.Add(-idleTime)
}

// 非测试
if !l.test {
// 禁止抢占
mp := acquirem()
// 遍历所有p
for _, pp := range allp {
typ, duration := pp.limiterEvent.consume(now)
switch typ {
case limiterEventIdleMarkWork: // 空闲辅助标记
fallthrough
case limiterEventIdle: // 空闲
idleTime += duration
sched.idleTime.Add(duration)
case limiterEventMarkAssist: // 辅助标记
fallthrough
case limiterEventScavengeAssist: // 辅助回收
assistTime += duration
case limiterEventNone: // 无
break
default:
throw("invalid limiter event type found")
}
}
releasem(mp)
}

// 助攻积分 => 花费在GC上的时间
windowGCTime := assistTime
// 还在标记阶段
if l.gcEnabled {
// =助攻积分+25%的总耗时
windowGCTime += int64(float64(windowTotalTime) * gcBackgroundUtilization)
}

// 总耗时-=p空闲辅助标记耗时
windowTotalTime -= idleTime

// 根据mutator耗时跟gc耗时判断是否需要限制GC运行
// 总耗时-助攻积分(耗时) => 剩下的就是非GC耗时,助攻积分(GC耗时)
l.accumulate(windowTotalTime-windowGCTime, windowGCTime)
}

// 根据mutator耗时跟gc耗时判断是否需要限制GC运行
func (l *gcCPULimiterState) accumulate(mutatorTime, gcTime int64) {
// 整个逻辑有点像TCP的慢启动、拥塞避免

// 系统发放的额度,范围:[0,fill]
headroom := l.bucket.capacity - l.bucket.fill
// fill==capacity => 额度为0
enabled := headroom == 0

// 欠债=gc耗时-mutator耗时
change := gcTime - mutatorTime

// gc耗时超过mutator耗时 and 额度不足
if change > 0 && headroom <= uint64(change) {
// 超额累计到overflow
l.overflow += uint64(change) - headroom
// 水桶满了
l.bucket.fill = l.bucket.capacity
// 以前没限速过
if !enabled {
// 开启限速
l.enabled.Store(true)
// 记录限速开始的GC轮次
l.lastEnabledCycle.Store(memstats.numgc + 1)
}
return
}

// gc耗时小于或等于mutator耗时(回收额度) or 额度足够

// 防止fill小于0
if change < 0 && l.bucket.fill <= uint64(-change) {
// 直接清0
l.bucket.fill = 0
} else {
// 继续释放额度
l.bucket.fill -= uint64(-change)
}

// 之前是限速状态
if change != 0 && enabled {
// 取消限速
l.enabled.Store(false)
}
}

GMP调度相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// 发起GC时,已经有其他G运行(同一个周期),挂起等待(_GCmarktermination时唤醒)
func gcWaitOnMark(n uint32) {
for {
// 加锁
lock(&work.sweepWaiters.lock)
// 正在执行的周期
nMarks := work.cycles.Load()
// GC还没发起
if gcphase != _GCmark {
// 预判下一个周期
nMarks++
}

// 是否是同一个周期
if nMarks > n {
// 可以继续执行
unlock(&work.sweepWaiters.lock)
return
}

// 同一个周期

// 把g放到链表
work.sweepWaiters.list.push(getg())

// 当前g让出CPU,g0执行调度运行其他g
// 在内部g、m解除绑定后会解锁sweepWaiters.lock
goparkunlock(&work.sweepWaiters.lock, waitReasonWaitForGCCycle, traceBlockUntilGCEnds, 1)
// 被唤醒
}
}

// 同步标记,如果允许user类型g运行则把阻塞的g全部放回全局队列并尝试获取p绑定m唤醒运行
func schedEnableUser(enable bool) {
// 调度器加锁
lock(&sched.lock)
// 已经设置了
if sched.disable.user == !enable {
// 解锁
unlock(&sched.lock)
return
}
// 同步
sched.disable.user = !enable
// 如果是允许user类型g运行
if enable {
// g数量
n := sched.disable.n
sched.disable.n = 0
// 把一批g放到全局队列
globrunqputbatch(&sched.disable.runnable, n)
// 解锁
unlock(&sched.lock)
// 如果g数量不为0 and 空闲p数量也不为0
for ; n != 0 && sched.npidle.Load() != 0; n-- {
// 从midle空闲链表中拿一个m绑定p并唤醒(可能拿不到p)
startm(nil, false, false)
}
} else {
// 解锁
unlock(&sched.lock)
}
}

// 更新p、调度器状态,把m放到midle空闲链表并挂起休眠,被唤醒后绑定一个p返回
func gcstopm() {
gp := getg()

// 状态不一致
if !sched.gcwaiting.Load() {
throw("gcstopm: not waiting for gc")
}
// m自旋中
if gp.m.spinning {
// 重置spinning
gp.m.spinning = false
// 复原,nmspinning-=1
if sched.nmspinning.Add(-1) < 0 {
// 小于0,异常
throw("gcstopm: negative nmspinning")
}
}
// 取消p与m的绑定
pp := releasep()
// 调度器加锁
lock(&sched.lock)
// p的状态设置为_Pgcstop
pp.status = _Pgcstop
// 当前时刻
pp.gcStopTime = nanotime()
// 计数器stopwait-=1
sched.stopwait--
// 所有的p都放到空闲队列了
if sched.stopwait == 0 {
// 唤醒GC相关的m(m放在stopnote.key)
notewakeup(&sched.stopnote)
}
// 解锁
unlock(&sched.lock)
// 把m放到midle空闲链表并挂起休眠,被唤醒后绑定一个p返回
stopm()
}

// 队列为空时执行netpoll轮询,检查有g返回true
func pollWork() bool {
// 全局队列不为空
if sched.runqsize != 0 {
return true
}
// p
p := getg().m.p.ptr()
// 本地队列不为空
if !runqempty(p) {
return true
}
// netpoll已初始化 and 挂起的g数量不为0 and 当前没有进行轮询
if netpollinited() && netpollAnyWaiters() && sched.lastpoll.Load() != 0 {
// 平台相关
// 执行epollWait检查,0-没有数据立即返回
if list, delta := netpoll(0); !list.empty() {
// 修改g状态放进本地/全局队列,并尝试唤醒m处理
injectglist(&list)
// 计数器+=delta => netpollWaiters+=delta
netpollAdjustWaiters(delta)
return true
}
}
return false
}

// 判断标记工作线程是否需要自我抢占,超过目标值返回true
func pollFractionalWorkerExit() bool {
// 当前时刻
now := nanotime()
// 当前周期GC耗时
delta := now - gcController.markStartTime
// 防御性编程
if delta <= 0 {
return true
}
// p
p := getg().m.p.ptr()
// 比例标记线程会多次启动、暂停,gcFractionalMarkTime是历史累计,gcMarkWorkerStartTime是当前运行耗时,相当于最后一次纪录
// 当前周期内的总耗时=比例标记任务下的耗时+当前比例标记线程的耗时
selfTime := p.gcFractionalMarkTime + (now - p.gcMarkWorkerStartTime)
// 当前周期内的总耗时/当前周期GC耗时 > 1.2*比例标记任务利用率目标 => 比例标记任务的CPU使用率是否超过目标值的1.2倍
return float64(selfTime)/float64(delta) > 1.2*gcController.fractionalUtilizationGoal
}

// 根据额度唤醒一批g,额度有剩余就累计到全局额度bgScanCredit(与gcParkAssist成对使用)
func gcFlushBgCredit(scanWork int64) {
// assist队列为空
if work.assistQueue.q.empty() {
// 额度累计到全局bgScanCredit
gcController.bgScanCredit.Add(scanWork)
return
}

// 转换参数
assistBytesPerWork := gcController.assistBytesPerWork.Load()
// 额度转换为字节数
scanBytes := int64(float64(scanWork) * assistBytesPerWork)

lock(&work.assistQueue.lock)
// assist队列不为空 and 额度大于0
for !work.assistQueue.q.empty() && scanBytes > 0 {
// 拿走队列第一个g
gp := work.assistQueue.q.pop()
// >=0 => 额度为正,不欠债
if scanBytes+gp.gcAssistBytes >= 0 {
// g的欠债,字节数
scanBytes += gp.gcAssistBytes
// 重置,不欠债
gp.gcAssistBytes = 0
// 将g放到p.runq队列,从空闲队列拿一个p和一个m绑定并唤醒(可能拿不到p)
// 放到队列尾部,降低优先级。这个g是gcParkAssist导致挂起的
ready(gp, 0, false)

// 额度还有数据,继续找下一个g
} else {
// <0,g还是欠债状态
// 更新g的额度
gp.gcAssistBytes += scanBytes
// 额度用完了
scanBytes = 0
// g放回队列,继续挂起等待
work.assistQueue.q.pushBack(gp)
// 额度用完了,剩下的g也不用处理了,退出循环
break
}
}

// 要么队列为空 or 要么额度不足

// 额度还有,那就是队列为空
if scanBytes > 0 {
// 转换参数
assistWorkPerByte := gcController.assistWorkPerByte.Load()
// 字节转换成额度
scanWork = int64(float64(scanBytes) * assistWorkPerByte)
// 剩余的额度累计到全局bgScanCredit
gcController.bgScanCredit.Add(scanWork)
}
unlock(&work.assistQueue.lock)
}

栈相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
// 释放栈
func stackfree(stk stack) {
gp := getg()
// stk.lo
v := unsafe.Pointer(stk.lo)
// 栈空间大小
n := stk.hi - stk.lo
// 非2的倍数
if n&(n-1) != 0 {
throw("stack not a power of 2")
}
// 异常
if stk.lo+n < stk.hi {
throw("bad stack size")
}
// 默认为0,忽略
if stackDebug >= 1 {
println("stackfree", v, n)
memclrNoHeapPointers(v, n) // for testing, clobber stack data
}
// debug.efence默认为0、stackFromSystem默认为0,忽略
if debug.efence != 0 || stackFromSystem != 0 {
if debug.efence != 0 || stackFaultOnFree != 0 {
sysFault(v, n)
} else {
sysFree(v, n, &memstats.stacks_sys)
}
return
}
// n < 2KB*2^4 and n < 32KB(一样的条件)
if n < fixedStack<<_NumStackOrders && n < _StackCacheSize {
// order索引,linux下有4个层级
order := uint8(0)
n2 := n
// >2KB
for n2 > fixedStack {
order++
n2 >>= 1
}
// stk.lo
x := gclinkptr(v)
// stackNoCache默认为0 or 没有p or 禁止抢占
if stackNoCache != 0 || gp.m.p == 0 || gp.m.preemptoff != "" {
lock(&stackpool[order].item.mu)
// 纪录mspan到stackpool、栈放回mspan.manualFreeList,符合条件则将mspan释放回系统
stackpoolfree(x, order)
unlock(&stackpool[order].item.mu)
} else {
// 有p and 可抢占
c := gp.m.p.ptr().mcache
// >= 32KB
if c.stackcache[order].size >= _StackCacheSize {
// 将一半的数据使用stackpoolfree释放
stackcacherelease(c, order)
}
// 放回stackcache
x.ptr().next = c.stackcache[order].list
c.stackcache[order].list = x
c.stackcache[order].size += n
}
} else {
// n >= 32KB
// 根据地址找到heapArena再找到mspan
s := spanOfUnchecked(uintptr(v))
// 非手动管理,异常
if s.state.get() != mSpanManual {
println(hex(s.base()), v)
throw("bad span state")
}

if gcphase == _GCoff {
// 清理阶段
// 释放栈
osStackFree(s)
mheap_.freeManual(s, spanAllocStack)
} else {
// GC运行中
// mspan可能被重用,状态修改可能跟GC冲突,直接放到stackLarge即可
log2npage := stacklog2(s.npages)
lock(&stackLarge.lock)
stackLarge.free[log2npage].insert(s)
unlock(&stackLarge.lock)
}
}
}

// 扫描栈帧内指针、defer链、panic、state.buf队列
func scanstack(gp *g, gcw *gcWork) int64 {
// 判断g的状态是不是_Gscanrunnable、_Gscansyscall、_Gscanwaiting

// 没有_Gscan标志,异常
if readgstatus(gp)&_Gscan == 0 {
print("runtime:scanstack: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", hex(readgstatus(gp)), "\n")
throw("scanstack - bad status")
}

// 取消_Gscan标志后检查
switch readgstatus(gp) &^ _Gscan {
default:
print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", readgstatus(gp), "\n")
throw("mark - bad status")
case _Gdead:
return 0
case _Grunning:
print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", readgstatus(gp), "\n")
throw("scanstack: goroutine not stopped")
case _Grunnable, _Gsyscall, _Gwaiting:
// 目标状态
}

// g0执行
if gp == getg() {
throw("can't scan our own stack")
}

// 栈指针
var sp uintptr
if gp.syscallsp != 0 {
// 处于系统调用
sp = gp.syscallsp
} else {
// 一般情况下
sp = gp.sched.sp
}
// 已使用栈大小
scannedSize := gp.stack.hi - sp

p := getg().m.p.ptr()
// 累计扫描栈大小
p.scannedStackSize += uint64(scannedSize)
// 累计扫描栈数量
p.scannedStacks++

// 是否允许栈缩小
if isShrinkStackSafe(gp) {
// 缩小栈(暂不深入研究)
shrinkstack(gp)
} else {
// 不允许缩小:g处于系统调用、异步安全点、挂起在channel、tracing开启并在等待用于GC
// 在下一个同步安全点缩小
gp.preemptShrink = true
}

var state stackScanState
state.stack = gp.stack

// stackTraceDebug默认false,忽略
if stackTraceDebug {
println("stack trace goroutine", gp.goid)
}

// debugScanConservative默认false,忽略
if debugScanConservative && gp.asyncSafePoint {
print("scanning async preempted goroutine ", gp.goid, " stack [", hex(gp.stack.lo), ",", hex(gp.stack.hi), ")\n")
}

// 扫描并保存ctxt寄存器
if gp.sched.ctxt != nil {
// 扫描一片内存,如果是指针则设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
scanblock(uintptr(unsafe.Pointer(&gp.sched.ctxt)), goarch.PtrSize, &oneptrmask[0], gcw, &state)
}

// 扫描栈
var u unwinder
for u.init(gp, 0); u.valid(); u.next() {
// (暂不深入研究)
scanframeworker(&u.frame, &state, gcw)
}

// 下面是defer跟panic扫描

// defer链
for d := gp._defer; d != nil; d = d.link {
// 扫描函数值
if d.fn != nil {
// 放在栈上的闭包函数
// 扫描一片内存,如果是指针则设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
scanblock(uintptr(unsafe.Pointer(&d.fn)), goarch.PtrSize, &oneptrmask[0], gcw, &state)
}

// 可能指向一个heap上分配的defer
if d.link != nil {
// 扫描一片内存,如果是指针则设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
scanblock(uintptr(unsafe.Pointer(&d.link)), goarch.PtrSize, &oneptrmask[0], gcw, &state)
}

// 常规方法无法访问的defer
if d.heap {
// 扫描一片内存,如果是指针则设置对象在mspan的gcmarkBits、pageMarks,累计bytesMarked,最后放入任务缓冲区
scanblock(uintptr(unsafe.Pointer(&d)), goarch.PtrSize, &oneptrmask[0], gcw, &state)
}
}

// panic
if gp._panic != nil {
// panic只会在栈上分配
// 指针放到state.buf队列
state.putPtr(uintptr(unsafe.Pointer(gp._panic)), false)
}

// 查找并扫描所有可达的栈对象

// 构造BST树
state.buildIndex()
for {
// 返回cbuf数组最后一个元素
p, conservative := state.getPtr()
if p == 0 {
break
}
// 根据指针找到对象
obj := state.findObject(p)
if obj == nil {
continue
}
r := obj.r
if r == nil {
// 已扫描
continue
}
// obj.r = nil => 防止重复扫描
obj.setRecord(nil)

// stackTraceDebug默认false,忽略
if stackTraceDebug {
printlock()
print(" live stkobj at", hex(state.stack.lo+uintptr(obj.off)), "of size", obj.size)
if conservative {
print(" (conservative)")
}
println()
printunlock()
}
ptrBytes, gcData := r.gcdata()
b := state.stack.lo + uintptr(obj.off)
if conservative {
scanConservative(b, ptrBytes, gcData, gcw, &state)
} else {
scanblock(b, ptrBytes, gcData, gcw, &state)
}
}

// 释放对象缓冲区(上一个循环中,指针缓冲区已释放)
for state.head != nil {
x := state.head
state.head = x.next
// stackTraceDebug默认false,忽略
if stackTraceDebug {
for i := 0; i < x.nobj; i++ {
obj := &x.obj[i]
if obj.r == nil { // reachable
continue
}
println(" dead stkobj at", hex(gp.stack.lo+uintptr(obj.off)), "of size", obj.r.size)
// Note: not necessarily really dead - only reachable-from-ptr dead.
}
}
x.nobj = 0
// wbuf放到work.empty
putempty((*workbuf)(unsafe.Pointer(x)))
}
// 有残留
if state.buf != nil || state.cbuf != nil || state.freeBuf != nil {
throw("remaining pointer buffers")
}
return int64(scannedSize)
}

bitmap操作相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// 从gcBitsArenas分配足以容纳nelems个位的内存(64的倍数向上取整)
func newMarkBits(nelems uintptr) *gcBits {
// 计算需要多少个块
blocksNeeded := (nelems + 63) / 64
// 每个块8个字节(64位)
bytesNeeded := blocksNeeded * 8

// 当前arena
head := (*gcBitsArena)(atomic.Loadp(unsafe.Pointer(&gcBitsArenas.next)))
// 尝试从head分配bytesNeeded个字节
if p := head.tryAlloc(bytesNeeded); p != nil {
// 分配成功
return p
}

// 分配失败,head要么为nil,要么空间不足,新建一个新的arena

// 加锁
lock(&gcBitsArenas.lock)
// double-check
if p := gcBitsArenas.next.tryAlloc(bytesNeeded); p != nil {
// 分配成功
unlock(&gcBitsArenas.lock)
return p
}

// 确定空间不足

// 从gcBitsArenas申请一个arena,没有则向系统申请64KB大小内存用作新的arena(期间会解锁)
fresh := newArenaMayUnlock()
// double-check
if p := gcBitsArenas.next.tryAlloc(bytesNeeded); p != nil {
// 把新的arena放到free链表
fresh.next = gcBitsArenas.free
gcBitsArenas.free = fresh
unlock(&gcBitsArenas.lock)
return p
}

// 从新的arena分配
p := fresh.tryAlloc(bytesNeeded)
// 分配失败
if p == nil {
throw("markBits overflow")
}

// 把新的arena放到next链表
fresh.next = gcBitsArenas.next
// gcBitsArenas.next = fresh
atomic.StorepNoWB(unsafe.Pointer(&gcBitsArenas.next), unsafe.Pointer(fresh))

unlock(&gcBitsArenas.lock)
return p
}

// 从gcBitsArenas申请一个arena,没有则向系统申请64KB大小内存用作新的arena
func newArenaMayUnlock() *gcBitsArena {
var result *gcBitsArena
// free链表没有足够空间
if gcBitsArenas.free == nil {
unlock(&gcBitsArenas.lock)
// 向系统申请64KB大小内存(Reserved),最低64MB,创建arenaHint、heapArena
result = (*gcBitsArena)(sysAlloc(gcBitsChunkBytes, &memstats.gcMiscSys))
// 申请失败
if result == nil {
throw("runtime: cannot allocate memory")
}
// 成功
lock(&gcBitsArenas.lock)
} else {
// free链表还有足够空间
// 拿走free链表第一个节点
result = gcBitsArenas.free
gcBitsArenas.free = gcBitsArenas.free.next
// 清0
memclrNoHeapPointers(unsafe.Pointer(result), gcBitsChunkBytes)
}

// 重置next指针
result.next = nil
// 偏移量设置8字节对齐
if unsafe.Offsetof(gcBitsArena{}.bits)&7 == 0 {
result.free = 0
} else {
result.free = 8 - (uintptr(unsafe.Pointer(&result.bits[0])) & 7)
}
return result
}

参考文档

Memory Optimization and Garbage Collector Management in Go
A Guide to the Go Garbage Collector
Go: How Does the Garbage Collector Mark the Memory?
Go Does Not Need a Java Style GC
Go 语言的 GC 实现分析
一文弄懂 Golang GC、三色标记、混合写屏障机制
Golang垃圾回收(GC)介绍
How does go GC identify GC roots
What is a garbage collection (GC) root?
关于Golang GC的一些误解–真的比Java算法更领先吗?
golang 垃圾回收(五)混合写屏障