Golang源码探索----GC的实现原理(6)
推荐文章:
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)相关推荐
- Golang源码探索(三) GC的实现原理
Golang从1.5开始引入了三色GC, 经过多次改进, 当前的1.9版本的GC停顿时间已经可以做到极短. 停顿时间的减少意味着"最大响应时间"的缩短, 这也让go更适合编写网络服 ...
- 【golang源码分析】chan底层原理——附带读写用户队列的环形缓冲区
1 环形缓冲区 1.1 环形缓冲区结构 环形缓冲区通常有一个读指针和一个写指针.读指针指向环形缓冲区中可读的数据,写指针指向环形缓冲区中可写的缓冲区.通过移动读指针和写指针就可以实现缓冲区的数据读取和 ...
- Golang源码探索(一) 编译和调试源码
GO可以说是近几年最热门的新兴语言之一了, 一般人看到分布式和大数据就会想到GO, 这个系列的文章会通过研究golang的源代码来分析内部的实现原理, 和CoreCLR不同的是, golang的源代码 ...
- CoreCLR源码探索(八) JIT的工作原理(详解篇)
在上一篇 我们对CoreCLR中的JIT有了一个基础的了解,这一篇我们将更详细分析JIT的实现. JIT的实现代码主要在https://github.com/dotnet/coreclr/tree/m ...
- CoreCLR源码探索(四) GC内存收集器的内部实现 分析篇
在这篇中我将讲述GC Collector内部的实现, 这是CoreCLR中除了JIT以外最复杂部分,下面一些概念目前尚未有公开的文档和书籍讲到. 为了分析这部分我花了一个多月的时间,期间也多次向Cor ...
- c++ map 获取key列表_好未来Golang源码系列一:Map实现原理分析
分享老师:学而思网校 郭雨田 一.map的结构与设计原理 golang中map是一个kv对集合.底层使用hash table,用链表来解决冲突 ,出现冲突时,不是每一个key都申请一个结构通过链表串起 ...
- CoreCLR源码探索(五) GC内存收集器的内部实现 调试篇
在上一篇中我分析了CoreCLR中GC的内部处理, 在这一篇我将使用LLDB实际跟踪CoreCLR中GC,关于如何使用LLDB调试CoreCLR的介绍可以看: 微软官方的文档,地址 我在第3篇中的介绍 ...
- golang源码分析-调度概述
golang源码分析-调度过程概述 本文主要概述一下golang的调度器的大概工作的流程,众所周知golang是基于用户态的协程的调度来完成多任务的执行.在Linux操作系统中,以往的多线程执行都是通 ...
- golang源码分析-启动过程概述
golang源码分析-启动过程概述 golang语言作为根据CSP模型实现的一种强类型的语言,本文主要就是通过简单的实例来分析一下golang语言的启动流程,为深入了解与学习做铺垫. golang代码 ...
最新文章
- 编写 Debugging Tools for Windows 扩展,第 2 部分:输出 (windbg 插件 扩展)
- 【测试】ESP32天线信号强度比较,小龟小车A2天线esp32cam板载外置天线测试数据...
- Android 应用目录分析
- 云溪怎么导入dxf_dwg怎么转换成dxf文件?超详细图文教程分享
- js 月份加6个月_美国切削工具6月份订单较上月增加10.1
- 多库多事务降低数据不一致概率
- php图片滑动代码,基于mootools 1.3框架下的图片滑动效果代码_Mootools
- 如何发布第一个属于自己的npm包
- 江苏计算机类事业编总分多少,必看!江苏事业单位统考三类岗位分值分布
- python处理xps文件_WFP: 读取XPS文件或将word、txt文件转化为XPS文件
- springboot基于微信小程序的高校学生疫情在校封闭管理系统的设计与实现毕业设计源码240904
- 差分技术:LVDS(低电压差分信号)
- 通过Windows批处理脚本批量修改DNS
- 想要学计算机最好是哪所大学,盘点丨计算机专业最好的7所大学
- 大写金额换算器iOS版源代码
- 【Vue学习笔记】尚硅谷Vue2.0+Vue3.0全套教程丨vue.js从入门到精通
- PyCharm:安装/搭建/配置/插件
- 安卓程序开发需要学习哪些语言
- 使用多个icon 字体图标库样式冲突问题
- python scapy使用教程_scapy的基本用法