Open northfun opened 3 years ago
从现在2021-04-18开始每天至少记录一个问题的回答描述,mark想不到的细节,逐一查明。
TODO:The Journey of Go's Garbage Collector
Go:Go1.5后使用三色并发标记。初始所有变量都为白色,从goroutine的栈开始标记可达白色节点,放入灰色集。从灰色集中取出节点,将该节点可达的白色节点放入灰色集,并将取出的灰色节点标记为黑色。重复上述步骤,直到灰色集和没有节点。剩余的白色节点便为不可达节点,可以回收。 Go1.8之前因为要全栈扫描,STW会有明显停顿。通过加入混合内存屏障(Yuasa-style deletion write barrier [Yuasa '90] and a Dijkstra-style insertion write barrier)缩短STW时间。三色标记的原则:没有引用白色节点的黑色节点。 问题:1. 如何标记;2. 加入灰色集是什么操作;3.混合内存屏障原理; (以下为Proposal: Eliminate STW stack re-scanning的理解)
三色标记的原则:没有引用白色节点的黑色节点。
writePointer(slot, ptr): shade(ptr) // shade:当ptr不是灰色或者黑色时,在被引用前将其标记为灰色 *slot = ptr
writePointer(slot, ptr): shade(*slot) *slot = ptr
writePointer(slot, ptr): shade(*slot) if current stack is grey: shade(ptr) *slot = ptr
和Dijkstra写屏障不同的是,混合内存屏障允许标记为黑色的goroutine栈(已经扫描过)可以有指向白色对象的指针,而不shade那个白色对象。但它遵从弱三色原则(invariant):任何被黑色对象引用的白色对象可以通过一系列白色指针被其他灰色对象引用到。Yuasa写屏障需要在开始标记时STW,以执行全栈扫描或者做快照,但是无需再次扫描。Dijkstra写屏障支持并发开始标记,但需要在回收前STW,执行全栈扫描。混合写屏障继承了二者的优势,既可以做到在开始GC标记时并发扫描,又可以保证栈最初被扫描后便一直为黑色。
弱三色原则(invariant):任何被黑色对象引用的白色对象可以通过一系列白色指针被其他灰色对象引用到
混合内存屏障的缺点是会导致更多的浮动垃圾,因为它保留了标记阶段中任何时候都可以从根(堆栈除外)访问的所有内容(TODO:root)。但事实上当前(2016年,Go1.5)单纯用Dijkstra也差不多如此。混合屏障还禁止某些优化:特别地,如果Go编译器可以静态显示指针为nil,当前会省略写入屏障,但是在这种情况下,混合屏障需要写入屏障。这可能会稍微增加二进制文件大小。
一些其它考虑:
GODEBUG=cgocheck=1
问题1:如何实现的三色标记? 从GOROOT/src/runtime的func GC()开始。
func GC() -> func gcStart(trigger gcTrigger) -> // Go的GMP模型中的P(逻辑处理器)通过mcache分配<=32kB的空间, // 其中<=16kB的空间从mcache中的*tiny分配, // (为了看“标记在哪”这个问题,可以只用tiny举个 ) func gcMarkTinyAllocs() -> // 终于看到三色之灰色函数了, // mspan是go管理内存的基本单位, // 这里的span是上一步tiny中获取的 func greyobject(obj, base, off uintptr, span *mspan, gcw *gcWork, objIndex uintptr) -> // 这里,首先标记的是mspan.gcmarkBits // (gcmarkBits *gcBits,这个gcBits类似uint8的别名,是记录某位置对象是否被引用的bitmap起始位置——被引用的对象会被标记为灰色), // 其次,从span的首地址找到全局堆(mheap_)对应的arena块中的页位置, // 进而标记arena.pageMarks数组,也类似bitmap func (s *mspan) markBitsForIndex(objIndex uintptr) markBits // 在这个方法最后,会判断span是否被标记为不用扫描(span内没指针) // 如果是,则执行:gcw.bytesMarked += uint64(span.elemsize),就算把span标黑了 // 这个gcw是P中的GC工作缓冲区(P's GC work buffer cache) ///////////....../////////////// // 这个函数用于将灰色标记为黑色,直到“很难”找到可标记的 gcDrainN(gcw *gcWork, scanWork int64) int64
Java:分代收集算法。
挑选合适的程序(goroutine)在线程执行,三个问题:
Go:sysmon。 GMP模型,G为goroutine;M为真实线程;P为G运行的上下文,P需要绑定M才可以执行G。 Java:
Go: Java: JVM
Go: Java: Shiro
Go: Go语言之父谈Slice,如何优雅地关闭Channel Java:
TODO:The Journey of Go's Garbage Collector
GC
Go:Go1.5后使用三色并发标记。初始所有变量都为白色,从goroutine的栈开始标记可达白色节点,放入灰色集。从灰色集中取出节点,将该节点可达的白色节点放入灰色集,并将取出的灰色节点标记为黑色。重复上述步骤,直到灰色集和没有节点。剩余的白色节点便为不可达节点,可以回收。 Go1.8之前因为要全栈扫描,STW会有明显停顿。通过加入混合内存屏障(Yuasa-style deletion write barrier [Yuasa '90] and a Dijkstra-style insertion write barrier)缩短STW时间。
三色标记的原则:没有引用白色节点的黑色节点。
问题:1. 如何标记;2. 加入灰色集是什么操作;3.混合内存屏障原理; (以下为Proposal: Eliminate STW stack re-scanning的理解)和Dijkstra写屏障不同的是,混合内存屏障允许标记为黑色的goroutine栈(已经扫描过)可以有指向白色对象的指针,而不shade那个白色对象。但它遵从
弱三色原则(invariant):任何被黑色对象引用的白色对象可以通过一系列白色指针被其他灰色对象引用到
。Yuasa写屏障需要在开始标记时STW,以执行全栈扫描或者做快照,但是无需再次扫描。Dijkstra写屏障支持并发开始标记,但需要在回收前STW,执行全栈扫描。混合写屏障继承了二者的优势,既可以做到在开始GC标记时并发扫描,又可以保证栈最初被扫描后便一直为黑色。混合内存屏障的缺点是会导致更多的浮动垃圾,因为它保留了标记阶段中任何时候都可以从根(堆栈除外)访问的所有内容(TODO:root)。但事实上当前(2016年,Go1.5)单纯用Dijkstra也差不多如此。混合屏障还禁止某些优化:特别地,如果Go编译器可以静态显示指针为nil,当前会省略写入屏障,但是在这种情况下,混合屏障需要写入屏障。这可能会稍微增加二进制文件大小。
一些其它考虑:
GODEBUG=cgocheck=1
检查,除非禁用这个检查否则违反这个规则程序会panic。问题1:如何实现的三色标记? 从GOROOT/src/runtime的func GC()开始。
Java:分代收集算法。
CSP
挑选合适的程序(goroutine)在线程执行,三个问题:
Go:sysmon。 GMP模型,G为goroutine;M为真实线程;P为G运行的上下文,P需要绑定M才可以执行G。 Java:
内存模型
Go: Java: JVM
组件
Go: Java: Shiro
原理
Go: Go语言之父谈Slice,如何优雅地关闭Channel Java: