Golang-GC相关问题
Go中GC流程
Go1.14版本以STW为界限,可以将GC划分为五个阶段:
- GCMark 标记准备阶段,为并发标记做准备工作,启动写屏障
- STWGCMark扫描标记阶段,与赋值器并发执行,写屏障开启并发
- GCMarkTermination 标记终止阶段,保证一个周期内标记任务完成,停止写屏障
- GCoff 内存清扫阶段,将需要回收的内存归还到堆中,写屏障关闭GCoff
- 内存归还阶段,将过多的内存归还给操作系统,写屏障关闭。
GC触发机制
- 主动触发:调用
runtime.GC
- 被动触发:
- 使用系统监控,该触发条件由
runtime.forcegcperiod
变量控制,默认为2分钟。当超过两分钟没有产生任何GC时,强制触发GC。 - 使用步调(Pacing)算法,其核心思想是控制内存增长的比例。如Go的GC 是一种比例GC,下一次GC结束时的堆大小和上一次GC存活堆大小成比例.
- 使用系统监控,该触发条件由
GC调优
通过go tool pprof
和 go tool trace
等工具
- 控制内存分配的速度,限制
Goroutine
的数量,从而提高赋值器对CPU的利用率。 - 减少并复用内存,例如使用
sync.Pool
来复用需要频繁创建临时对象,例如,提前分配足够的内存来降低多余的拷贝。 - 需要时,增大
GOGC
的值,降低GC的运行频率。
Go的内存模型中为什么小对象多了会引发垃圾回收(GC)压力?
内存分配和回收的开销:Go语言的垃圾回收器是基于标记-清除算法的,它需要遍历内存中的对象来标记和回收不再使用的对象。对于小对象,特别是大量小对象的分配和回收,会导致频繁的GC操作。这是因为小对象的数量多,回收器需要不断扫描和标记这些对象,增加了GC的开销。
内存碎片问题:分配和回收小对象可能会导致内存碎片问题。当小对象被分配和回收后,它们的内存空间可能会在堆上留下一些碎片,这些碎片可能无法有效地被重复利用。随着时间的推移,内存中可能会出现许多不同大小的碎片,最终导致了内存浪费和GC的效率下降。
垃圾回收频率增加:由于小对象的分配和回收频繁,垃圾回收可能会更频繁地运行。这会导致应用程序的吞吐量下降,因为垃圾回收会占用CPU时间,导致应用程序的暂停时间增加。
为了减少小对象引起的垃圾回收压力,可以采取以下一些策略:
对象池(Object Pooling):对象池是一种技术,它允许重复使用已分配的对象,而不是频繁地分配和回收它们。这可以减少GC的频率和内存碎片。
避免频繁分配和回收:尽量避免频繁地分配和回收小对象,特别是在循环中。可以考虑重用对象或者使用更大的缓冲区,减少分配次数。
调整堆大小和GC参数:通过调整Go程序的堆大小和GC参数,可以尝试减少GC的频率。可以使用
GODEBUG
环境变量来监控GC的行为,并进行适当的调整。使用内存分配分析工具:Go提供了一些内存分配分析工具,如
pprof
,可以帮助你识别内存分配和垃圾回收的瓶颈,并优化你的代码。
总之,小对象的频繁分配和回收可能会增加垃圾回收的压力,因此在编写Go代码时,需要考虑如何优化内存使用,以减少GC的频率和内存碎片问题。