对垃圾回收的掌握,似乎不能只局限于背一个笼统的4步曲,还是要尝试更深入的理解才行。
本文仅供个人学习使用,参考了外部资料,非原创。
参考
何时触发?
- 分配对象时触发。
- 触发后,判断堆内存达阈值,才真正执行gc。
- 定时触发,forcegchelper协程。
- 触发后,判断上次执行已经是2min之前,才真正执行gc。
- 用户手动触发。
- 判断上一轮GC已经结束, 才真正执行gc。
为了更多的体现结合源码,可以这样回答:
触发GC时,会调用gcTrigger.test方法, 该方法会结合触发事件的类型,进行触发条件的校验。如果是gcTriggerHeap说明是分配对象时触发;如果是gcTriggerTime说明是定时触发;如果是gcTriggerCycle用户手动触发。
也就是说:触发GC,不一定会真正执行GC。
定时触发
- 在 runtime 包启动时(init),会异步启动一个 forcegchelper协程。该协程通过for循环 + park阻塞的方式触发gc,触发的类型为 gcTriggerTime。
- 在 runtime 包的main函数中,有一个 systemstack() 方法,该方法负责切换到g0, 并且调用sysmon方法。
- sysmon方法中,会循环调用gcTrigger.test方法,在gcTrigger.test方法,判断上次gc的时间减去当前时间是否大于120 * 10的9次方纳秒(2min)。
- 如果满足条件,gcTrigger.test方法返回true。将 forcegchelper 协程添加到 gList 中,并在 injectglist 方法内将其唤醒。
注意,这个时间间隔是写死在代码里的: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。
手动触发
手动触发没啥说的,gcTrigger.test只需要交验上一轮gc是否结束。