上卷 程序设计
中卷 标准库
- 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
下卷 运行时
源码剖析
附录
2.4 回收
内存回收由垃圾回收器引发,内存分配器执行。
缓存
缓存部件(cache)是垃圾回收作根对象。在 mark 之前,它们所持有的 span 被重新放回 central 。如此,剩余闲置内存(span.objects)就可调配给其他 cache 使用,避免浪费。
// mgcmark.go func markroot(gcw *gcWork, i uint32) { flushmcache(...) }
// mstats.go // flushmcache flushes the mcache of allp[i]. func flushmcache(i int) { p := allp[i] c := p.mcache c.releaseAll() stackcache_clear(c) }
扩容时,cache 每次从 central 提取一个 span,但不保证会将该 span 内的 object 全部分配出去。
如此,剩余内存会被闲置。将包含剩余空间的 span 重新放回 central,就可转交给其他 cache,从而平衡资源使用,避免内存浪费。
不用担心一个 span 被多个 cache 使用会有什么影响。 分配操作只是从内存块中 “切” 出一个片段交给某个对象,剩余空间后续如何,与之无关。
// mcache.go func (c *mcache) releaseAll() { // 遍历内存块数组。 for i := range c.alloc { s := c.alloc[i] // 如果是合法内存块,上交给 central。 if s != &emptymspan { mheap_.central[i].mcentral.uncacheSpan(s) c.alloc[i] = &emptymspan } } // 释放微⼩小对象分配内存。 c.tiny = 0 c.tinyoffset = 0 }
emptymspan 用于占位,并不持有内存。
// dummy MSpan that contains no free objects. var emptymspan mspan
收归 central 时,根据是否有剩余空间决定存储列表。
上述操作是将所有 span 上交,并不仅仅是有剩余空间的。兴许某个内存块正好内存耗尽,缓存部件尚未及扩容。
// mcentral.go // Return span from an mcache. // // s must have a span class corresponding to this // mcentral and it must not be empty. func (c *mcentral) uncacheSpan(s *mspan) { if !go115NewMCentralImpl { c.oldUncacheSpan(s) return } sg := mheap_.sweepgen stale := s.sweepgen == sg+1 // 修正代龄。 if stale { atomic.Store(&s.sweepgen, sg-1) } else { atomic.Store(&s.sweepgen, sg) } // 剩余空间。 n := int(s.nelems) - int(s.allocCount) if stale { // 清理结束会放回相关集合内。 s.sweep(false) } else { // 按是否有剩余空间,放置合适的集合内。 if n > 0 { c.partialSwept(sg).push(s) } else { c.fullSwept(sg).push(s) } } }
清理
垃圾标记完成后,以 span 为单位进行清理(sweep)操作。
所谓清理,并非挨个处理所有 object。
实际上,内存块除分配位图(allocBits)外,还有个一摸一样的垃圾标记位图(gcmarkBits)。
垃圾回收器以此标记出可回收,也就是可重新使用的内存位置。
如此,只需将 markBits 数据 “复制” 给 allocBits 就可实现清理目的。
至于内存单元里的遗留数据清除与否,则是分配操作要考虑的。
// mheap.go type mspan struct { gcmarkBits *gcBits // 标记位图 allocBits *gcBits // 分配位图 }
// mgcsweep.go // Sweep frees or collects finalizers for blocks not marked in the mark phase. // It clears the mark bits in preparation for the next GC round. // Returns true if the span was returned to heap. // If preserve=true, don't return it to heap nor relink in mcentral lists; // caller takes care of it. func (s *mspan) sweep(preserve bool) bool { if !go115NewMCentralImpl { return s.oldSweep(preserve) } spc := s.spanclass sweepgen := mheap_.sweepgen // 基于标记位图统计已分配数量 (不含可回收部分)。 nalloc := uint16(s.countAlloc()) // 计算本次回收数量 (标记前分配数量 - 标记后分配数量)。 nfreed := s.allocCount - nalloc // 重置属性。 s.allocCount = nalloc s.freeindex = 0 // 将标记位图直接当作分配位图使⽤,便可实现 “复制”。 s.allocBits = s.gcmarkBits s.gcmarkBits = newMarkBits(s.nelems) // 初始化缓存。 s.refillAllocCache(0) atomic.Store(&s.sweepgen, sweepgen) if spc.sizeclass() != 0 { // 小对象 ... if !preserve { // 回收后分配数量为 0,则整体归还给 heap。 if nalloc == 0 { mheap_.freeSpan(s) return true } // 按是否有剩余空间,放置到 central 合适集合中。 if uintptr(nalloc) == s.nelems { mheap_.central[spc].mcentral.fullSwept(sweepgen).push(s) } else { mheap_.central[spc].mcentral.partialSwept(sweepgen).push(s) } } } else if !preserve { // 大对象 ... if nfreed != 0 { // 归还给 heap。 mheap_.freeSpan(s) return true } // 没能回收空间,放到 central。 // 大对象内存块只有一个 object,没有回收,就表示对象依然存活。 mheap_.central[spc].mcentral.fullSwept(sweepgen).push(s) } return false }
如果 central 持有的 span 收回其全部空间,则将其上交给 heap,以便调剂给其他 central 使用。
这是第二级资源平衡,使得不同 sizeclass 的请求能充分利用已有内存。
为什么上交的是完整收回空间的 span ?
因为每个 span 仅服务于一种 sizeclass,如果将剩余空间转给其他 central,就必须将其剩余空间切分成新的 span。
这么做将导致内存碎片化,最终得不偿失。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论