概括

Go的垃圾回收官方形容为 非分代 非紧缩 写屏障 三色并发标记清理算法。

非分代:不像Java那样分为年轻代和年老代,自然也没有minor gc和maj o gc的区别。

非紧缩:在垃圾回收之后不会进行内存整理以清除内存碎片。

写屏障:在并发标记的过程中,如果应用程序(mutator)修改了对象图,就可能出现标记遗漏的可能,写屏障就是为了处理标记遗漏的问题。

三色:将GC中的对象按照搜索的情况分成三种:

黑色: 对象在这次GC中已标记,且这个对象包含的子对象也已标记

灰色: 对象在这次GC中已标记, 但这个对象包含的子对象未标记

白色: 对象在这次GC中未标记

并发:可以和应用程序(mutator)在一定程度上并发执行。

标记清理:GC算法分为两个大步骤:标记阶段找出要回收的对象,清理阶段则回收未被标记的对象(要被回收的对象)

触发时机

gcTriggerAlways: 强制触发GC,没找到什么情况下使用这个

gcTriggerHeap: 当前分配的内存达到一定值(动态计算)就触发GC

gcTriggerTime: 当一定时间(2分钟)没有执行过GC就触发GC

gcTriggerCycle: 要求启动新一轮的GC, 已启动则跳过, 手动触发GC的runtime.GC()会使用这个条件

func gcStart(mode gcMode, trigger gcTrigger) {

// Since this is called from malloc and malloc is called in

// the guts of a number of libraries that might be holding

// locks, don't attempt to start GC in non-preemptible or

// potentially unstable situations.

mp := acquirem()

if gp := getg(); gp == mp.g0 || mp.locks > 1 || mp.preemptoff != "" {

releasem(mp)

return

}

releasem(mp)

mp = nil

// 检查GC条件是否满足,和下面的test()构成双检查锁,如果满足GC条件但目前处于GC清理阶段,那就参与清理

for trigger.test() && gosweepone() != ^uintptr(0) {

sweep.nbgsweep++

}

// 加锁检查

semacquire(&work.startSema)

if !trigger.test() {

semrelease(&work.startSema)

return

}

/*************** ..... *****************/

}

在trigger.test()函数中,检查是否满足GC触发的条件

func (t gcTrigger) test() bool {

if !memstats.enablegc || panicking != 0 {

return false

}

if t.kind == gcTriggerAlways {

return true

}

if gcphase != _GCoff {

return false

}

switch t.kind {

case gcTriggerHeap:

// Non-atomic access to heap_live for performance. If

// we are going to trigger on this, this thread just

// atomically wrote heap_live anyway and we'll see our

// own write.

return memstats.heap_live >= memstats.gc_trigger

case gcTriggerTime:

if gcpercent < 0 {

return false

}

lastgc := int64(atomic.Load64(&memstats.last_gc_nanotime))

// forcegcperiod = 2分钟

return lastgc != 0 && t.now-lastgc > forcegcperiod

case gcTriggerCycle:

// t.n > work.cycles, but accounting for wraparound.

return int32(t.n-work.cycles) > 0

}

return true

}

const (

// gcTriggerAlways indicates that a cycle should be started

// unconditionally, even if GOGC is off or we're in a cycle

// right now. This cannot be consolidated with other cycles.

gcTriggerAlways gcTriggerKind = iota

// gcTriggerHeap indicates that a cycle should be started when

// the heap size reaches the trigger heap size computed by the

// controller.

gcTriggerHeap

// gcTriggerTime indicates that a cycle should be started when

// it's been more than forcegcperiod nanoseconds since the

// previous GC cycle.

gcTriggerTime

// gcTriggerCycle indicates that a cycle should be started if

// we have not yet started cycle number gcTrigger.n (relative

// to work.cycles).

gcTriggerCycle

)

算法过程

Sweep Termination: 对未清扫的span进行清扫, 只有上一轮的GC的清扫工作完成才可以开始新一轮的GC

Mark: 扫描所有根对象, 和根对象可以到达的所有对象, 标记它们不被回收

Mark Termination: 完成标记工作, 重新扫描部分根对象(要求STW)

Sweep: 按标记结果清扫span

golang_gc.jpg

func gcStart(mode gcMode, trigger gcTrigger) {

// 拿到锁,保证只有一个执行流进入到这个临界区

semacquire(&worldsema)

// 启动后台扫描任务(G)

if mode == gcBackgroundMode {

gcBgMarkStartWorkers()

}

gcResetMarkState()

work.stwprocs, work.maxprocs = gomaxprocs, gomaxprocs

if work.stwprocs > ncpu {

work.stwprocs = ncpu

}

work.heap0 = atomic.Load64(&memstats.heap_live)

work.pauseNS = 0

work.mode = mode

now := nanotime()

work.tSweepTerm = now

work.pauseStart = now

if trace.enabled {

traceGCSTWStart(1)

}

systemstack(stopTheWorldWithSema)

// Finish sweep before we start concurrent scan.

systemstack(func() {

finishsweep_m()

})

// clearpools before we start the GC. If we wait they memory will not be

// reclaimed until the next GC cycle.

clearpools()

work.cycles++

if mode == gcBackgroundMode { // Do as much work concurrently as possible

gcController.startCycle()

work.heapGoal = memstats.next_gc

// Enter concurrent mark phase and enable

// write barriers.

setGCPhase(_GCmark)

gcBgMarkPrepare() // Must happen before assist enable.

gcMarkRootPrepare()

// Mark all active tinyalloc blocks. Since we're

// allocating from these, they need to be black like

// other allocations. The alternative is to blacken

// the tiny block on every allocation from it, which

// would slow down the tiny allocator.

gcMarkTinyAllocs()

// At this point all Ps have enabled the write

// barrier, thus maintaining the no white to

// black invariant. Enable mutator assists to

// put back-pressure on fast allocating

// mutators.

atomic.Store(&gcBlackenEnabled, 1)

// Assists and workers can start the moment we start

// the world.

gcController.markStartTime = now

// Concurrent mark.

systemstack(func() {

now = startTheWorldWithSema(trace.enabled)

})

work.pauseNS += now - work.pauseStart

work.tMark = now

}

semrelease(&work.startSema)

}

关键函数及路径:

gcBgMarkStartWorkers():准备后台标记工作goroutine(allp), 启动后等待该任务通知信号量bgMarkReady再继续,notewakeup(&work.bgMarkReady)

gcResetMarkState():重置一些全局状态和所有gorontine的栈(一种根对象)扫描状态

systemstack(stopTheWorldWithSema):启动stop the world

systemstack(func(){finishsweep_m()}): 不断去除要清理的span进行清理,然后重置gcmark位

clearpools(): 清扫sched.sudogcache和sched.deferpool,不知道在干嘛......

gcController.startCycle():启动新一轮GC,设置gc controller的状态位和计算一些估计值

setGCPhase(_GCmark):设置GC阶段,启用写屏障

gcBgMarkPrepare():设置后台标记任务计数;work.nproc = ^uint32(0),work.nwait = ^uint32(0)

gcMarkRootPrepare(): 计算扫描根对象的任务数量

gcMarkTinyAllocs(): 标记所有tiny alloc等待合并的对象

atomic.Store(&gcBlackenEnabled, 1): 启用辅助GC

systemstack(func(){now=startTheWorldWithSema(trace.enable)}): 停止stop the world

func gcBgMarkWorker(_p_ *p) {

/********** ....... ***********/

// 通知gcBgMarkStartWorkers可以继续处理

notewakeup(&work.bgMarkReady)

for {

// 切换到g0运行

systemstack(func() {

// Mark our goroutine preemptible so its stack

// can be scanned. This lets two mark workers

// scan each other (otherwise, they would

// deadlock). We must not modify anything on

// the G stack. However, stack shrinking is

// disabled for mark workers, so it is safe to

// read from the G stack.

casgstatus(gp, _Grunning, _Gwaiting)

switch _p_.gcMarkWorkerMode {

default:

throw("gcBgMarkWorker: unexpected gcMarkWorkerMode")

case gcMarkWorkerDedicatedMode:

gcDrain(&_p_.gcw, gcDrainUntilPreempt|gcDrainFlushBgCredit)

if gp.preempt {

lock(&sched.lock)

for {

gp, _ := runqget(_p_)

if gp == nil {

break

}

globrunqput(gp)

}

unlock(&sched.lock)

}

// Go back to draining, this time

// without preemption.

gcDrain(&_p_.gcw, gcDrainNoBlock|gcDrainFlushBgCredit)

case gcMarkWorkerFractionalMode:

gcDrain(&_p_.gcw, gcDrainFractional|gcDrainUntilPreempt|gcDrainFlushBgCredit)

case gcMarkWorkerIdleMode:

gcDrain(&_p_.gcw, gcDrainIdle|gcDrainUntilPreempt|gcDrainFlushBgCredit)

}

casgstatus(gp, _Gwaiting, _Grunning)

})

/******** ...... ***********/

// 判断是否所有后台标记任务都完成, 并且没有更多的任务

if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {

gcMarkDone()

}

}

}

gcDrain()是执行标记的函数

当所有标记任务完成时,执行gcMarkDone()函数

func gcDrain(gcw *gcWork, flags gcDrainFlags) {

initScanWork := gcw.scanWork

// 如果根对象未扫描完,则先扫描根对象,Jobs为根对象总数,next相当于一个对象任务的取数器

if work.markrootNext < work.markrootJobs {

for !(preemptible && gp.preempt) {

job := atomic.Xadd(&work.markrootNext, +1) - 1

if job >= work.markrootJobs {

break

}

// 将会扫描根对象,并把它加入到标记队列gcWork中之中,也就是把对象变成灰色

markroot(gcw, job)

if check != nil && check() {

goto done

}

}

}

// 当根对象全部put到标记队列中, 消费标记队列,根据对象图进行消费

for !(preemptible && gp.preempt) {

if work.full == 0 {

gcw.balance()

}

var b uintptr

if blocking {

b = gcw.get()

} else {

b = gcw.tryGetFast()

if b == 0 {

b = gcw.tryGet()

}

}

if b == 0 {

// work barrier reached or tryGet failed.

break

}

scanobject(b, gcw)

// 如果已经扫描了一定数量的对象(gcCreditSlack的值是2000)

if gcw.scanWork >= gcCreditSlack {

// 把扫描的对象数量添加到全局

atomic.Xaddint64(&gcController.scanWork, gcw.scanWork)

// 减少辅助GC的工作量和唤醒等待中的G

if flushBgCredit {

gcFlushBgCredit(gcw.scanWork - initScanWork)

initScanWork = 0

}

idleCheck -= gcw.scanWork

gcw.scanWork = 0

// 如果是idle模式且达到了检查的扫描量, 则检查是否有其他任务(G), 如果有则跳出循环

if idle && idleCheck <= 0 {

idleCheck += idleCheckThreshold

if pollWork() {

break

}

}

}

}

done:

// 把扫描的对象数量添加到全局

if gcw.scanWork > 0 {

atomic.Xaddint64(&gcController.scanWork, gcw.scanWork)

// 减少辅助GC的工作量和唤醒等待中的G

if flushBgCredit {

gcFlushBgCredit(gcw.scanWork - initScanWork)

}

gcw.scanWork = 0

}

}

func gcMarkDone() {

semacquire(&work.markDoneSema)

// Re-check transition condition under transition lock.

if !(gcphase == _GCmark && work.nwait == work.nproc && !gcMarkWorkAvailable(nil)) {

semrelease(&work.markDoneSema)

return

}

// 暂时禁止启动新的后台标记任务

atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, -0xffffffff)

prevFractionalGoal := gcController.fractionalUtilizationGoal

gcController.fractionalUtilizationGoal = 0

// 转换到Mark Termination阶段,进入STW阶段

systemstack(stopTheWorldWithSema)

// 标记对根对象的扫描已完成

work.markrootDone = true

// 禁止辅助GC和后台任务

atomic.Store(&gcBlackenEnabled, 0)

// 唤醒所有因为辅助GC而休眠的G

gcWakeAllAssists()

semrelease(&work.markDoneSema)

// 计算下一次触发gc需要的heap大小

nextTriggerRatio := gcController.endCycle()

// 计算下一次触发gc需要的heap大小

gcMarkTermination(nextTriggerRatio)

}

func gcMarkTermination(nextTriggerRatio float64) {

// 禁止辅助GC和后台标记任务的运行

// 重新允许本地标记队列(下次GC使用)

// 设置当前GC阶段到完成标记阶段, 并启用写屏障

atomic.Store(&gcBlackenEnabled, 0)

gcBlackenPromptly = false

setGCPhase(_GCmarktermination)

systemstack(func() {gcMark(startTime)})

systemstack(func() {

// 设置当前GC阶段到关闭, 并禁用写屏障

setGCPhase(_GCoff)

// 唤醒后台清扫任务, 将在STW结束后开始运行

gcSweep(work.mode)

})

// 更新下一次触发gc需要的heap大小(gc_trigger)

gcSetTriggerRatio(nextTriggerRatio)

// 重置清扫状态

sweep.nbgsweep = 0

sweep.npausesweep = 0

// 统计执行GC的次数然后唤醒等待清扫的G

lock(&work.sweepWaiters.lock)

memstats.numgc++

injectglist(work.sweepWaiters.head.ptr())

work.sweepWaiters.head = 0

unlock(&work.sweepWaiters.lock)

// 重新启动世界

systemstack(func() { startTheWorldWithSema(true) })

// 移动标记队列使用的缓冲区到自由列表, 使得它们可以被回收

prepareFreeWorkbufs()

// 释放未使用的栈

systemstack(freeStackSpans)

semrelease(&worldsema)

// 重新允许当前的G被抢占

releasem(mp)

mp = nil

当标记的扫描工作完成之后,会进入到GC Mark Termination阶段,也就是gcMarkDone()函数,关键路径:

systemstack(stopTheWorldWithSema):启动STW

gcWakeAllAssists():唤醒所有因辅助gc而休眠的G

nextTriggerRatio:=gcController.endCycle():计算下一次触发gc需要的heap大小

setGCPhase(_GCmarktermination):启用写屏障

systemstack(func() {gcMark(startTime)}): 再次执行标记

systemstack(func(){setGCPhase(_GCoff);gcSweep(work.mode)}):关闭写屏障,唤醒后台清扫任务,将在STW结束后开始运行

gcSetTriggerRatio(nextTriggerRatio):更新下次触发gc时的heap大小

systemstack(func() { startTheWorldWithSema(true) }): 停止STW

STW分析:web程序中,我们关注最大停顿时间

STW出现在两个位置,分别是在初始标记阶段Mark和并发标记完成后重标记Mark Termination:

初始标记阶段:

systemstack(stopTheWorldWithSema):启动stop the world

systemstack(func(){finishsweep_m()}): 不断去除要清理的span进行清理,然后重置gcmark位

clearpools(): 清扫sched.sudogcache和sched.deferpool,不知道在干嘛......

gcController.startCycle():启动新一轮GC,设置gc controller的状态位和计算一些估计值

gcMarkRootPrepare(): 计算扫描根对象的任务数量

gcMarkTinyAllocs(): 涂灰所有tiny alloc等待合并的对象

systemstack(func(){now=startTheWorldWithSema(trace.enable)}): 停止stop the world

找出其中比较耗时的阶段:

finishsweep_m():如果上一次GC清扫阶段没有完成,那么在新的一轮GC阶段中就会在阻塞在这里,使得原本可以和应用程序并行的清扫阶段被放进STW。所以,如果频繁的执行GC,就可能会使得GC的最大停顿时间变长。

clearpools():时间复杂度大概为:O(5*L),L为_defer中链表的长度。

gcController.startCycle():O(P),P为go的P的数量,和cpu数有关,时间复杂度可以忽略

gcMarkRootPrepare(): O(全局变量区),包括bss段和data段

gcMarkTinyAllocs(): O(P)

个人觉得,对STW影响最大的是finishsweep_m()阶段,所有我们应该尽量避免让go在清扫期执行新一轮的GC。

重新标记阶段

systemstack(stopTheWorldWithSema):启动STW

gcWakeAllAssists():唤醒所有因辅助gc而休眠的G

nextTriggerRatio:=gcController.endCycle():计算下一次触发gc需要的heap大小

setGCPhase(_GCmarktermination):启用写屏障

systemstack(func() {gcMark(startTime)}): 再次执行标记

systemstack(func(){setGCPhase(_GCoff);gcSweep(work.mode)}):关闭写屏障,唤醒后台清扫任务,将在STW结束后开始运行

gcSetTriggerRatio(nextTriggerRatio):更新下次触发gc时的heap大小

systemstack(func() { startTheWorldWithSema(true) }): 停止STW

找出其中比较耗时的阶段:

gcWakeAllAssists():O(G),将所有可运行的G插入到调度链表

systemstack(func() {gcMark(startTime)}):

go java gc_Golang GC算法相关推荐

  1. Java常见GC算法_垃圾收集器及内存分配_G1垃圾收集器

    常见GC算法 引用计数法: 每个对象都有一个计数器, 对象被引用一次, 计数器+1, 当对象引用失败一次. 计数器-1, 当对象计数器等于0, 说明对象没有被应用, 就可GC 优: 运行过程中, 可随 ...

  2. 深入理解 Java 虚拟机 - 你了解 GC 算法原理吗

    来自:好好学Java 虚拟机系列文章 深入理解 Java 虚拟机(第一弹) - Java 内存区域透彻分析 深入理解 Java 虚拟机(第二弹) - 常用 vm 参数分析 深入理解 Java 虚拟机- ...

  3. java标志清理_JVM内存管理之GC算法精解(五分钟让你彻底明白标记/清除算法)...

    相信不少猿友看到标题就认为LZ是标题党了,不过既然您已经被LZ忽悠进来了,那就好好的享受一顿算法大餐吧.不过LZ丑话说前面哦,这篇文章应该能让各位彻底理解标记/清除算法,不过倘若各位猿友不能在五分钟内 ...

  4. 《深入理解java虚拟机》笔记2——GC算法与内存分配策略

    说起垃圾收集(Garbage Collection, GC),想必大家都不陌生,它是JVM实现里非常重要的一环,JVM成熟的内存动态分配与回收技术使Java(当然还有其他运行在JVM上的语言,如Sca ...

  5. JVM(3):Java GC算法 垃圾收集器

    概述 垃圾收集 Garbage Collection 通常被称为"GC",它诞生于1960年 MIT 的 Lisp 语言,经过半个多世纪,目前已经十分成熟了. jvm 中,程序计数 ...

  6. 26 Java GC算法 垃圾收集器、标记 -清除算法、复制算法、标记-压缩算法、分代收集算法

    26.Java GC算法 垃圾收集器 1.1.1标记 -清除算法 1.1.2复制算法 1.1.3标记-压缩算法 1.1.4分代收集算法 26.Java GC算法 垃圾收集器 概述 垃圾收集 Garba ...

  7. Java内存组成GC算法

    Java内存组成&GC算法 @(JAVA)[java] Java内存组成GC算法 一内存组成 一Java程序的内存组成 1Java堆 2方法区含常量池永久代 3栈 1Java虚拟机栈 2本地方 ...

  8. java虚拟机和javaGC_Java虚拟机(三):GC算法和种类

    一.介绍 GC(Garbage Collection),垃圾收集 Java中,GC的对象是堆空间和永久区 二.GC算法 1. 引用计数法 老牌垃圾回收算法 通过引用计算来回收垃圾 Java中未使用,使 ...

  9. Java的GC机制及算法

    转载自   Java的GC机制及算法 GC的阶段  对每个对象而言,垃圾回收分为两个阶段:finalization和reclamation. finalization: 指运行这个对象的finaliz ...

  10. Java虚拟机-第二篇-GC算法与内存分配策略

    2019独角兽企业重金招聘Python工程师标准>>> GC引入 在Java的运行时数据区中,程序计数器.虚拟机栈.本地方法栈三个区域都是线程私有的,随线程而生,随线程而灭,在方法结 ...

最新文章

  1. 验证码的三种做法[纯数字][纯汉字][字母\数字\汉字混合]C#
  2. ORACLE 几个我忍了他很多年的问题
  3. python和java一样吗-Java与Python到底有什么区别和共同点详细对比
  4. 通用扩展函数--类型转换
  5. centos7修改服务器密码,centos7 上搭建MQTT服务器,修改密码,设置权限并进行测试。...
  6. 熟悉 Linux 系统结构,推开Linux 大门
  7. Spring Cloud Bus 消息总线介绍
  8. [洛谷2397]yyy loves Maths VI
  9. 游戏服务器的架构设计(一点参考,实际价值似乎不大……)
  10. 逻辑思维与C/C++解题
  11. 使用Freetype发现的问题记录
  12. 自动驾驶 从入门到精通 学习资料汇总 Week1~Week7
  13. 2022最新酷盒iApp源码V7.8版+内置超多功能
  14. DiskFileUpload上传与Spring的CommonsMultipartResolver上传对比
  15. 丁向荣单片机pdf_STC系列增强型8051单片机原理与应用
  16. python开发cms_Wagtail介绍 — 基于Django的Python CMS
  17. 3DMax设置长度单位
  18. RTX自动配置客户端服务器地址
  19. OpenGL地球(自转+贴图)
  20. dva自定义组件及使用方法

热门文章

  1. Jmeter遇到打不开的问题
  2. ArcGis 10.2运行提示“未授权”的解决方法
  3. 服务器中java项目调用Kettle转换脚本ktr
  4. linux 执行 ktr脚本,kettle在linux下面用于shell脚本执行:转换或者作业
  5. 将计算机设置成交换机主机,如何将路由器变成交换机功能 路由器变成交换机运用方式介绍【详解】...
  6. centos配置虚拟主机(站点)
  7. Convolution1D与Convolution2D区别
  8. js实现23种设计模式
  9. 上线啦,可以定时周期性提醒群成员的机器人
  10. 在macOS系统下外接硬盘安装windows10虚拟机