上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
3.4.2 标记
并发模式下,触发回收的用户 goroutine 在启动回收周期后,继续执行用户代码。
阻塞模式下,用户 goroutine 被暂停(gosched),转而参与垃圾回收执行。
gcDrain
每个 P 都有本地缓存,另有全局队列进行平衡。
有关缓存队列,请参考《其他 - 缓存队列》。
// runtime2.go type p struct { gcw gcWork }
// mgc.go var work struct { full lfstack }
标记工作从根对象开始,然后才处理灰色队列。
// mgc.go var work struct { markrootNext uint32 // next markroot job markrootJobs uint32 // number of markroot jobs }
// mgcmark.go func gcDrain(gcw *gcWork, flags gcDrainFlags) { // 根对象。 if work.markrootNext < work.markrootJobs { for !(gp.preempt && (preemptible || atomic.Load(&sched.gcwaiting) != 0)) { job := atomic.Xadd(&work.markrootNext, +1) - 1 if job >= work.markrootJobs { break } markroot(gcw, job) // 检查是否要处理其他任务(小时工、临时工)。 if check != nil && check() { goto done } } } // 灰色队列。 for !(gp.preempt && (preemptible || atomic.Load(&sched.gcwaiting) != 0)) { // 如果全局队列已空,则从本地转移一批过去。 if work.full == 0 { gcw.balance() } // 提取一个对象。 b := gcw.tryGetFast() if b == 0 { b = gcw.tryGet() // 本地、全局。 } if b == 0 { break } // 扫描该对象。 scanobject(b, gcw) } done: }
markroot
统计所有根对象数量,然后按序处理。
// mgcmark.go func markroot(gcw *gcWork, i uint32) { baseFlushCache := uint32(fixedRootCount) baseData := baseFlushCache + uint32(work.nFlushCacheRoots) baseBSS := baseData + uint32(work.nDataRoots) baseSpans := baseBSS + uint32(work.nBSSRoots) baseStacks := baseSpans + uint32(work.nSpanRoots) end := baseStacks + uint32(work.nStackRoots) switch { case baseFlushCache <= i && i < baseData: flushmcache(int(i - baseFlushCache)) case baseData <= i && i < baseBSS : ... case baseBSS <= i && i < baseSpans : ... case i == fixedRootFinalizers : ... case i == fixedRootFreeGStacks : ... case baseSpans <= i && i < baseStacks : ... default: // 扫描 G.stack ... gp = allgs[i-baseStacks] scanstack(gp, gcw) } }
根对象扫描标记(包括 markrootBlock、scanstack)最终调用 scanblock 函数。
根对象有自己的独立位图。
// mgcmark.go // scanblock scans b as scanobject would, but using an explicit // pointer bitmap instead of the heap bitmap. // This is used to scan non-heap roots, so it does not update // gcw.bytesMarked or gcw.scanWork. func scanblock(b0, n0 uintptr, ptrmask *uint8, gcw *gcWork, stk *stackScanState) { b := b0 n := n0 for i := uintptr(0); i < n; { // 读取标记位。 bits := uint32(*addb(ptrmask, i/(sys.PtrSize*8))) if bits == 0 { i += sys.PtrSize * 8 continue } for j := 0; j < 8 && i < n; j++ { // 如果标记为 1,表示指针。 if bits & 1 != 0 { // 利用指针获取目标,并将其放入本地队列。 p := *(*uintptr)(unsafe.Pointer(b + i)) if p != 0 { if obj, span, objIndex := findObject(p, b, i); obj != 0 { greyobject(obj, b, i, span, gcw, objIndex) } } } // 右移,处理下一个标记位。 bits >>= 1 i += sys.PtrSize } } }
scanobject
扫描对象内容,将指针字段所引用对象标记为灰色,放入本地队列。
为对象分配内存时,会在 heapArena.bitmap 记录指针信息。
// malloc.go func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { noscan := typ == nil || typ.ptrdata == 0 // 如果包含指针,则需要在 bitmap 内添加记录。 if !noscan { heapBitsSetType(uintptr(x), size, dataSize, typ) } // 垃圾回收期间,新建对象直接标记为黑色。 if gcphase != _GCoff { gcmarknewobject(span, uintptr(x), size, scanSize) } }
如果对象包含指针,那么会按其长度进行对齐。
比对 bitmap 记录,找到指针字段,将所引用对象加入灰色队列。
// mgcmark.go // scanobject scans the object starting at b, adding pointers to gcw. func scanobject(b uintptr, gcw *gcWork) { // 从 heapArena 获取 bitmap 和 span。 hbits := heapBitsForAddr(b) s := spanOfUnchecked(b) n := s.elemsize // span 内 object 大小。 // 大于 128KB 的大对象,分割扫描。 if n > maxObletBytes { // 如果是从起始地址开始,那么表示尚未分隔。 if b == s.base() { // 如果是 noscan,表示不包含指针,直接黑色即可。 if s.spanclass.noscan() { gcw.bytesMarked += uint64(n) return } // 以 128KB 为单位,分割成多个 oblet,放入本地队列。 for oblet := b + maxObletBytes; oblet < s.base()+s.elemsize; oblet += maxObletBytes { if !gcw.putFast(oblet) { gcw.put(oblet) } } } // 分割后,确定对象长度。 n = s.base() + s.elemsize - b if n > maxObletBytes { n = maxObletBytes } } // 按指针长度遍历对象内容(sizeclass 1 = 8)。 // 如果是包含指针的复合结构,那么必然按指针长度对齐。 // 配合标记位进行检查。 for i = 0; i < n; i += sys.PtrSize { bits := hbits.bits() if i != 1 * sys.PtrSize && bits & bitScan == 0 { break // no more pointers in this object } if bits & bitPointer == 0 { continue // not a pointer } // 如果是指针字段,访问目标对象。 obj := *(*uintptr)(unsafe.Pointer(b + i)) if obj != 0 && obj - b >= n { // 将目标灰色对象放入队列。 if obj, span, objIndex := findObject(obj, b, i); obj != 0 { greyobject(obj, b, i, span, gcw, objIndex) } } } gcw.bytesMarked += uint64(n) gcw.scanWork += int64(i) }
greyobject
灰色对象(存活)需要在 span.gcMarkBits 标记。
非 debug 模式,greyobject 会将标记结果保存在 gcMarkBits 里。
然后在 mspan.sweep 时,将 gcMarkBits 复制为 allocBits。
// mgcmark.go func greyobject(obj, base, off uintptr, span *mspan, gcw *gcWork, objIndex uintptr) { // 从 span.gcmarkBits 获取标记。 mbits := span.markBitsForIndex(objIndex) // 避免重复标记。 if mbits.isMarked() { return } // 设置标记。 mbits.setMarked() // 如不包含指针,直接为黑色,无须放入队列。 if span.spanclass.noscan() { gcw.bytesMarked += uint64(span.elemsize) return } // 放入本地队列。 if !gcw.putFast(obj) { gcw.put(obj) } }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论