[Golang 修仙之路] Go语言基础:垃圾回收

57 阅读2分钟

对垃圾回收的掌握,似乎不能只局限于背一个笼统的4步曲,还是要尝试更深入的理解才行。

本文仅供个人学习使用,参考了外部资料,非原创。

参考

何时触发?

  1. 分配对象时触发。
    • 触发后,判断堆内存达阈值,才真正执行gc。
  2. 定时触发,forcegchelper协程。
    • 触发后,判断上次执行已经是2min之前,才真正执行gc。
  3. 用户手动触发。
    • 判断上一轮GC已经结束, 才真正执行gc。

为了更多的体现结合源码,可以这样回答:

触发GC时,会调用gcTrigger.test方法, 该方法会结合触发事件的类型,进行触发条件的校验。如果是gcTriggerHeap说明是分配对象时触发;如果是gcTriggerTime说明是定时触发;如果是gcTriggerCycle用户手动触发。

也就是说:触发GC,不一定会真正执行GC。

定时触发

  1. 在 runtime 包启动时(init),会异步启动一个 forcegchelper协程。该协程通过for循环 + park阻塞的方式触发gc,触发的类型为 gcTriggerTime。
  2. 在 runtime 包的main函数中,有一个 systemstack() 方法,该方法负责切换到g0, 并且调用sysmon方法。
  3. sysmon方法中,会循环调用gcTrigger.test方法,在gcTrigger.test方法,判断上次gc的时间减去当前时间是否大于120 * 10的9次方纳秒(2min)。
  4. 如果满足条件,gcTrigger.test方法返回true。将 forcegchelper 协程添加到 gList 中,并在 injectglist 方法内将其唤醒。

image.png

注意,这个时间间隔是写死在代码里的:var forcegcperiod int64 = 2 * 60 * 1e9 用户并不能配置。

分配对象时触发

runtime/malloc.go 中的分配对象方法malloc会在满足以下2个条件中任意一个时,触发gcTriggerHeap。

  • 尝试分配一个大于32KB的大对象时。
  • mcache中对应spanClass的mspan中空间已经用尽。

触发gc并不一定真的会执行gc,还会根据当前堆内存是否超过 上一轮设定的阈值来判断,超过了才会真的执行gc(调用gcStart,开始gc)。

  • 阈值 = 本轮gc后,堆大小 + (上一轮gc时栈大小 + 上一轮gc时全局变量大小 + 本轮gc后,堆大小)* (P/100)
  • P是用户可以配置的参数,默认100. 也就是说,默认允许堆内存增长2倍左右再GC。

image.png

手动触发

手动触发没啥说的,gcTrigger.test只需要交验上一轮gc是否结束。