推荐文章:

Golang源码探索----GC的实现原理(1)

Golang源码探索----GC的实现原理(2)

Golang源码探索----GC的实现原理(3)

Golang源码探索----GC的实现原理(4)

Golang源码探索----GC的实现原理(5)

span的sweep函数用于清扫单个span:

  1// Sweep frees or collects finalizers for blocks not marked in the mark phase.2// It clears the mark bits in preparation for the next GC round.3// Returns true if the span was returned to heap.4// If preserve=true, don't return it to heap nor relink in MCentral lists;5// caller takes care of it.6//TODO go:nowritebarrier7func (s *mspan) sweep(preserve bool) bool {8    // It's critical that we enter this function with preemption disabled,9    // GC must not start while we are in the middle of this function.10    _g_ := getg()11    if _g_.m.locks == 0 && _g_.m.mallocing == 0 && _g_ != _g_.m.g0 {12        throw("MSpan_Sweep: m is not locked")13    }14    sweepgen := mheap_.sweepgen15    if s.state != mSpanInUse || s.sweepgen != sweepgen-1 {16        print("MSpan_Sweep: state=", s.state, " sweepgen=", s.sweepgen, " mheap.sweepgen=", sweepgen, "\n")17        throw("MSpan_Sweep: bad span state")18    }19    if trace.enabled {20        traceGCSweepSpan(s.npages * _PageSize)21    }22    // 统计已清理的页数23    atomic.Xadd64(&mheap_.pagesSwept, int64(s.npages))24    spc := s.spanclass25    size := s.elemsize26    res := false27    c := _g_.m.mcache28    freeToHeap := false29    // The allocBits indicate which unmarked objects don't need to be30    // processed since they were free at the end of the last GC cycle31    // and were not allocated since then.32    // If the allocBits index is >= s.freeindex and the bit33    // is not marked then the object remains unallocated34    // since the last GC.35    // This situation is analogous to being on a freelist.36    // 判断在special中的析构器, 如果对应的对象已经不再存活则标记对象存活防止回收, 然后把析构器移到运行队列37    // Unlink & free special records for any objects we're about to free.38    // Two complications here:39    // 1. An object can have both finalizer and profile special records.40    //    In such case we need to queue finalizer for execution,41    //    mark the object as live and preserve the profile special.42    // 2. A tiny object can have several finalizers setup for different offsets.43    //    If such object is not marked, we need to queue all finalizers at once.44    // Both 1 and 2 are possible at the same time.45    specialp := &s.specials46    special := *specialp47    for special != nil {48        // A finalizer can be set for an inner byte of an object, find object beginning.49        objIndex := uintptr(special.offset) / size50        p := s.base() + objIndex*size51        mbits := s.markBitsForIndex(objIndex)52        if !mbits.isMarked() {53            // This object is not marked and has at least one special record.54            // Pass 1: see if it has at least one finalizer.55            hasFin := false56            endOffset := p - s.base() + size57            for tmp := special; tmp != nil && uintptr(tmp.offset) < endOffset; tmp = tmp.next {58                if tmp.kind == _KindSpecialFinalizer {59                    // Stop freeing of object if it has a finalizer.60                    mbits.setMarkedNonAtomic()61                    hasFin = true62                    break63                }64            }65            // Pass 2: queue all finalizers _or_ handle profile record.66            for special != nil && uintptr(special.offset) < endOffset {67                // Find the exact byte for which the special was setup68                // (as opposed to object beginning).69                p := s.base() + uintptr(special.offset)70                if special.kind == _KindSpecialFinalizer || !hasFin {71                    // Splice out special record.72                    y := special73                    special = special.next74                    *specialp = special75                    freespecial(y, unsafe.Pointer(p), size)76                } else {77                    // This is profile record, but the object has finalizers (so kept alive).78                    // Keep special record.79                    specialp = &special.next80                    special = *specialp81                }82            }83        } else {84            // object is still live: keep special record85            specialp = &special.next86            special = *specialp87        }88    }89    // 除错用90    if debug.allocfreetrace != 0 || raceenabled || msanenabled {91        // Find all newly freed objects. This doesn't have to92        // efficient; allocfreetrace has massive overhead.93        mbits := s.markBitsForBase()94        abits := s.allocBitsForIndex(0)95        for i := uintptr(0); i < s.nelems; i++ {96            if !mbits.isMarked() && (abits.index < s.freeindex || abits.isMarked()) {97                x := s.base() + i*s.elemsize98                if debug.allocfreetrace != 0 {99                    tracefree(unsafe.Pointer(x), size)
100                }
101                if raceenabled {
102                    racefree(unsafe.Pointer(x), size)
103                }
104                if msanenabled {
105                    msanfree(unsafe.Pointer(x), size)
106                }
107            }
108            mbits.advance()
109            abits.advance()
110        }
111    }
112    // 计算释放的对象数量
113    // Count the number of free objects in this span.
114    nalloc := uint16(s.countAlloc())
115    if spc.sizeclass() == 0 && nalloc == 0 {
116        // 如果span的类型是0(大对象)并且其中的对象已经不存活则释放到heap
117        s.needzero = 1
118        freeToHeap = true
119    }
120    nfreed := s.allocCount - nalloc
121    if nalloc > s.allocCount {
122        print("runtime: nelems=", s.nelems, " nalloc=", nalloc, " previous allocCount=", s.allocCount, " nfreed=", nfreed, "\n")
123        throw("sweep increased allocation count")
124    }
125    // 设置新的allocCount
126    s.allocCount = nalloc
127    // 判断span是否无未分配的对象
128    wasempty := s.nextFreeIndex() == s.nelems
129    // 重置freeindex, 下次分配从0开始搜索
130    s.freeindex = 0 // reset allocation index to start of span.
131    if trace.enabled {
132        getg().m.p.ptr().traceReclaimed += uintptr(nfreed) * s.elemsize
133    }
134    // gcmarkBits变为新的allocBits
135    // 然后重新分配一块全部为0的gcmarkBits
136    // 下次分配对象时可以根据allocBits得知哪些元素是未分配的
137    // gcmarkBits becomes the allocBits.
138    // get a fresh cleared gcmarkBits in preparation for next GC
139    s.allocBits = s.gcmarkBits
140    s.gcmarkBits = newMarkBits(s.nelems)
141    // 更新freeindex开始的allocCache
142    // Initialize alloc bits cache.
143    s.refillAllocCache(0)
144    // 如果span中已经无存活的对象则更新sweepgen到最新
145    // 下面会把span加到mcentral或者mheap
146    // We need to set s.sweepgen = h.sweepgen only when all blocks are swept,
147    // because of the potential for a concurrent free/SetFinalizer.
148    // But we need to set it before we make the span available for allocation
149    // (return it to heap or mcentral), because allocation code assumes that a
150    // span is already swept if available for allocation.
151    if freeToHeap || nfreed == 0 {
152        // The span must be in our exclusive ownership until we update sweepgen,
153        // check for potential races.
154        if s.state != mSpanInUse || s.sweepgen != sweepgen-1 {
155            print("MSpan_Sweep: state=", s.state, " sweepgen=", s.sweepgen, " mheap.sweepgen=", sweepgen, "\n")
156            throw("MSpan_Sweep: bad span state after sweep")
157        }
158        // Serialization point.
159        // At this point the mark bits are cleared and allocation ready
160        // to go so release the span.
161        atomic.Store(&s.sweepgen, sweepgen)
162    }
163    if nfreed > 0 && spc.sizeclass() != 0 {
164        // 把span加到mcentral, res等于是否添加成功
165        c.local_nsmallfree[spc.sizeclass()] += uintptr(nfreed)
166        res = mheap_.central[spc].mcentral.freeSpan(s, preserve, wasempty)
167        // freeSpan会更新sweepgen
168        // MCentral_FreeSpan updates sweepgen
169    } else if freeToHeap {
170        // 把span释放到mheap
171        // Free large span to heap
172        // NOTE(rsc,dvyukov): The original implementation of efence
173        // in CL 22060046 used SysFree instead of SysFault, so that
174        // the operating system would eventually give the memory
175        // back to us again, so that an efence program could run
176        // longer without running out of memory. Unfortunately,
177        // calling SysFree here without any kind of adjustment of the
178        // heap data structures means that when the memory does
179        // come back to us, we have the wrong metadata for it, either in
180        // the MSpan structures or in the garbage collection bitmap.
181        // Using SysFault here means that the program will run out of
182        // memory fairly quickly in efence mode, but at least it won't
183        // have mysterious crashes due to confused memory reuse.
184        // It should be possible to switch back to SysFree if we also
185        // implement and then call some kind of MHeap_DeleteSpan.
186        if debug.efence > 0 {
187            s.limit = 0 // prevent mlookup from finding this span
188            sysFault(unsafe.Pointer(s.base()), size)
189        } else {
190            mheap_.freeSpan(s, 1)
191        }
192        c.local_nlargefree++
193        c.local_largefree += size
194        res = true
195    }
196    // 如果span未加到mcentral或者未释放到mheap, 则表示span仍在使用
197    if !res {
198        // 把仍在使用的span加到sweepSpans的"已清扫"队列中
199        // The span has been swept and is still in-use, so put
200        // it on the swept in-use list.
201        mheap_.sweepSpans[sweepgen/2%2].push(s)
202    }
203    return res
204}

从bgsweep和前面的分配器可以看出扫描阶段的工作是十分懒惰(lazy)的,
实际可能会出现前一阶段的扫描还未完成, 就需要开始新一轮的GC的情况,
所以每一轮GC开始之前都需要完成前一轮GC的扫描工作(Sweep Termination阶段).

GC的整个流程都分析完毕了, 最后贴上写屏障函数writebarrierptr的实现:

 1// NOTE: Really dst *unsafe.Pointer, src unsafe.Pointer,2// but if we do that, Go inserts a write barrier on *dst = src.3//go:nosplit4func writebarrierptr(dst *uintptr, src uintptr) {5    if writeBarrier.cgo {6        cgoCheckWriteBarrier(dst, src)7    }8    if !writeBarrier.needed {9        *dst = src
10        return
11    }
12    if src != 0 && src < minPhysPageSize {
13        systemstack(func() {
14            print("runtime: writebarrierptr *", dst, " = ", hex(src), "\n")
15            throw("bad pointer in write barrier")
16        })
17    }
18    // 标记指针
19    writebarrierptr_prewrite1(dst, src)
20    // 设置指针到目标
21    *dst = src
22}

writebarrierptr_prewrite1函数如下:

 1// writebarrierptr_prewrite1 invokes a write barrier for *dst = src2// prior to the write happening.3//4// Write barrier calls must not happen during critical GC and scheduler5// related operations. In particular there are times when the GC assumes6// that the world is stopped but scheduler related code is still being7// executed, dealing with syscalls, dealing with putting gs on runnable8// queues and so forth. This code cannot execute write barriers because9// the GC might drop them on the floor. Stopping the world involves removing
10// the p associated with an m. We use the fact that m.p == nil to indicate
11// that we are in one these critical p and throw if the write is of
12// a pointer to a heap object.
13//go:nosplit
14func writebarrierptr_prewrite1(dst *uintptr, src uintptr) {
15    mp := acquirem()
16    if mp.inwb || mp.dying > 0 {
17        releasem(mp)
18        return
19    }
20    systemstack(func() {
21        if mp.p == 0 && memstats.enablegc && !mp.inwb && inheap(src) {
22            throw("writebarrierptr_prewrite1 called with mp.p == nil")
23        }
24        mp.inwb = true
25        gcmarkwb_m(dst, src)
26    })
27    mp.inwb = false
28    releasem(mp)
29}

gcmarkwb_m函数如下:

 1func gcmarkwb_m(slot *uintptr, ptr uintptr) {2    if writeBarrier.needed {3        // Note: This turns bad pointer writes into bad4        // pointer reads, which could be confusing. We avoid5        // reading from obviously bad pointers, which should6        // take care of the vast majority of these. We could7        // patch this up in the signal handler, or use XCHG to8        // combine the read and the write. Checking inheap is9        // insufficient since we need to track changes to
10        // roots outside the heap.
11        //
12        // Note: profbuf.go omits a barrier during signal handler
13        // profile logging; that's safe only because this deletion barrier exists.
14        // If we remove the deletion barrier, we'll have to work out
15        // a new way to handle the profile logging.
16        if slot1 := uintptr(unsafe.Pointer(slot)); slot1 >= minPhysPageSize {
17            if optr := *slot; optr != 0 {
18                // 标记旧指针
19                shade(optr)
20            }
21        }
22        // TODO: Make this conditional on the caller's stack color.
23        if ptr != 0 && inheap(ptr) {
24            // 标记新指针
25            shade(ptr)
26        }
27    }
28}

shade函数如下:

 1// Shade the object if it isn't already.2// The object is not nil and known to be in the heap.3// Preemption must be disabled.4//go:nowritebarrier5func shade(b uintptr) {6    if obj, hbits, span, objIndex := heapBitsForObject(b, 0, 0); obj != 0 {7        gcw := &getg().m.p.ptr().gcw8        // 标记一个对象存活, 并把它加到标记队列(该对象变为灰色)9        greyobject(obj, 0, 0, hbits, span, gcw, objIndex)
10        // 如果标记了禁止本地标记队列则flush到全局标记队列
11        if gcphase == _GCmarktermination || gcBlackenPromptly {
12            // Ps aren't allowed to cache work during mark
13            // termination.
14            gcw.dispose()
15        }
16    }
17}
18参考链接
19https://github.com/golang/go
20https://making.pusher.com/golangs-real-time-gc-in-theory-and-practice
21https://github.com/golang/proposal/blob/master/design/17503-eliminate-rescan.md
22https://golang.org/s/go15gcpacing
23https://golang.org/ref/mem
24https://talks.golang.org/2015/go-gc.pdf
25https://docs.google.com/document/d/1ETuA2IOmnaQ4j81AtTGT40Y4_Jr6_IDASEKg0t0dBR8/edit#heading=h.x4kziklnb8fr
26https://go-review.googlesource.com/c/go/+/21503
27http://www.cnblogs.com/diegodu/p/5803202.html
28http://legendtkl.com/2017/04/28/golang-gc
29https://lengzzz.com/note/gc-in-golang
30Golang的GC和CoreCLR的GC对比
31因为我之前已经对CoreCLR的GC做过分析(看这一篇和这一篇), 这里我可以简单的对比一下CoreCLR和GO的GC实现:
32CoreCLR的对象带有类型信息, GO的对象不带, 而是通过bitmap区域记录哪些地方包含指针
33CoreCLR分配对象的速度明显更快, GO分配对象需要查找span和写入bitmap区域
34CoreCLR的收集器需要做的工作比GO多很多
35CoreCLR不同大小的对象都会放在一个segment中, 只能线性扫描
36CoreCLR判断对象引用要访问类型信息, 而go只需要访问bitmap
37CoreCLR清扫时要一个个去标记为自由对象, 而go只需要切换allocBits
38CoreCLR的停顿时间比GO要长
39虽然CoreCLR支持并行GC, 但是没有GO彻底, GO连扫描根对象都不需要完全停顿
40CoreCLR支持分代GC
41虽然Full GC时CoreCLR的效率不如GO, 但是CoreCLR可以在大部分时候只扫描第0和第1代的对象
42因为支持分代GC, 通常CoreCLR花在GC上的CPU时间会比GO要少
43CoreCLR的分配器和收集器通常比GO要高效, 也就是说CoreCLR会有更高的吞吐量.
44但CoreCLR的最大停顿时间不如GO短, 这是因为GO的GC整个设计都是为了减少停顿时间.
45现在分布式计算和横向扩展越来越流行,
46比起追求单机吞吐量, 追求低延迟然后让分布式解决吞吐量问题无疑是更明智的选择,
47GO的设计目标使得它比其他语言都更适合编写网络服务程序.

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

Golang语言社区

ID:GolangWeb

www.ByteEdu.Com

游戏服务器架构丨分布式技术丨大数据丨游戏算法学习

Golang源码探索----GC的实现原理(6)相关推荐

  1. Golang源码探索(三) GC的实现原理

    Golang从1.5开始引入了三色GC, 经过多次改进, 当前的1.9版本的GC停顿时间已经可以做到极短. 停顿时间的减少意味着"最大响应时间"的缩短, 这也让go更适合编写网络服 ...

  2. 【golang源码分析】chan底层原理——附带读写用户队列的环形缓冲区

    1 环形缓冲区 1.1 环形缓冲区结构 环形缓冲区通常有一个读指针和一个写指针.读指针指向环形缓冲区中可读的数据,写指针指向环形缓冲区中可写的缓冲区.通过移动读指针和写指针就可以实现缓冲区的数据读取和 ...

  3. Golang源码探索(一) 编译和调试源码

    GO可以说是近几年最热门的新兴语言之一了, 一般人看到分布式和大数据就会想到GO, 这个系列的文章会通过研究golang的源代码来分析内部的实现原理, 和CoreCLR不同的是, golang的源代码 ...

  4. CoreCLR源码探索(八) JIT的工作原理(详解篇)

    在上一篇 我们对CoreCLR中的JIT有了一个基础的了解,这一篇我们将更详细分析JIT的实现. JIT的实现代码主要在https://github.com/dotnet/coreclr/tree/m ...

  5. CoreCLR源码探索(四) GC内存收集器的内部实现 分析篇

    在这篇中我将讲述GC Collector内部的实现, 这是CoreCLR中除了JIT以外最复杂部分,下面一些概念目前尚未有公开的文档和书籍讲到. 为了分析这部分我花了一个多月的时间,期间也多次向Cor ...

  6. c++ map 获取key列表_好未来Golang源码系列一:Map实现原理分析

    分享老师:学而思网校 郭雨田 一.map的结构与设计原理 golang中map是一个kv对集合.底层使用hash table,用链表来解决冲突 ,出现冲突时,不是每一个key都申请一个结构通过链表串起 ...

  7. CoreCLR源码探索(五) GC内存收集器的内部实现 调试篇

    在上一篇中我分析了CoreCLR中GC的内部处理, 在这一篇我将使用LLDB实际跟踪CoreCLR中GC,关于如何使用LLDB调试CoreCLR的介绍可以看: 微软官方的文档,地址 我在第3篇中的介绍 ...

  8. golang源码分析-调度概述

    golang源码分析-调度过程概述 本文主要概述一下golang的调度器的大概工作的流程,众所周知golang是基于用户态的协程的调度来完成多任务的执行.在Linux操作系统中,以往的多线程执行都是通 ...

  9. golang源码分析-启动过程概述

    golang源码分析-启动过程概述 golang语言作为根据CSP模型实现的一种强类型的语言,本文主要就是通过简单的实例来分析一下golang语言的启动流程,为深入了解与学习做铺垫. golang代码 ...

最新文章

  1. 编写 Debugging Tools for Windows 扩展,第 2 部分:输出 (windbg 插件 扩展)
  2. 【测试】ESP32天线信号强度比较,小龟小车A2天线esp32cam板载外置天线测试数据...
  3. Android 应用目录分析
  4. 云溪怎么导入dxf_dwg怎么转换成dxf文件?超详细图文教程分享
  5. js 月份加6个月_美国切削工具6月份订单较上月增加10.1
  6. 多库多事务降低数据不一致概率
  7. php图片滑动代码,基于mootools 1.3框架下的图片滑动效果代码_Mootools
  8. 如何发布第一个属于自己的npm包
  9. 江苏计算机类事业编总分多少,必看!江苏事业单位统考三类岗位分值分布
  10. python处理xps文件_WFP: 读取XPS文件或将word、txt文件转化为XPS文件
  11. springboot基于微信小程序的高校学生疫情在校封闭管理系统的设计与实现毕业设计源码240904
  12. 差分技术:LVDS(低电压差分信号)
  13. 通过Windows批处理脚本批量修改DNS
  14. 想要学计算机最好是哪所大学,盘点丨计算机专业最好的7所大学
  15. 大写金额换算器iOS版源代码
  16. 【Vue学习笔记】尚硅谷Vue2.0+Vue3.0全套教程丨vue.js从入门到精通
  17. PyCharm:安装/搭建/配置/插件
  18. 安卓程序开发需要学习哪些语言
  19. 使用多个icon 字体图标库样式冲突问题
  20. python scapy使用教程_scapy的基本用法

热门文章

  1. java版mc复制tnt,教程/方块和物品复制
  2. 基于AndroidStudio员工绩效考核评价系统app设计
  3. 使用js 计算两个日期之间的相差的天数
  4. c语言编程镖局运镖,打点
  5. 某无人机飞控系统的原理、组成及各传感器的作用
  6. 据说很多搞软件的羡慕硬件工程师
  7. 在linux中安装gdb遇到的问题
  8. 重定向和转发的概念及区别
  9. 谷歌浏览器和火狐浏览器永久禁用缓存【一劳永逸的解决方式】
  10. Axure RP 9最新版软件及汉化包下载