GC基本原理和它的影响

164 阅读3分钟

概述

GC类型的语言遇到了性能问题,经常会怪罪于GC,但GC到底会影响什么,是不是周期性现象一定和GC有关,其实是非常不确定的。例如,无论是Go还是Java,STW的时间其实已经控制在了很短,除非是ms级别的超时,否则不可能是STW的问题。

details

gc调优的2个目标

降低gc的cpu开销

大部分情况,调优是针对CPU开销的,一般而言,只要不出现OOM,gc的CPU占比越小越好,因为gc占比越大,服务的吞吐量越小。常用的手段就是空间换时间,容忍更大的内存占用,减少gc的触发时间。

降低gc的stw时间,避免影响请求延时

现代gc的一个重要原则就是一次stw不会太长,一般控制在1ms以内。Go是如何做到的?Go只在2个阶段会进入stw,首先是栈root集合扫描时,其次是扫描完成时,这2个阶段都能做到和堆内存大小无关,因此都在ms或微妙级别。但也有一些延迟敏感型应用,需要进一步优化stw的时间。

Go gc

根对象

  • 全局变量
  • 执行栈:每个协程有自己的执行栈,包含栈变量和指向堆内存的指针
  • 寄存器

三色标记法

  • 白色:可回收的,未被访问的
  • 灰色:待扫描的,可能指向了白色对象
  • 黑色:已扫描的

写屏障和读屏障

写屏障和读屏障类似于一个被装饰的读写方法,当调用时可能会走到特殊的逻辑。

  • 写屏障是一个在并发垃圾回收器中才会出现的概念,它是为了打破gc的不正确的充要条件而设置的
  • 读屏障在复制算法中可能被用到

Go1.14的gc流程

  • gcStart
  • gcBgMarkPrepare/gcMarkRootPrepare/gcBgMarkWorker (stw)
  • gcBgMarkWorker/gcMarkDone
  • gcMarkTermination(stw)
  • gcSweep

Go的gc时机

  • 被动触发,2min一次
  • GOGC参数,内存占用大于之前的x倍,触发gc

Java gc

G1

分代算法

  • 年轻代:复制算法,MinorGC
  • 老年代:标记清除 标记整理,FullGC
  • 永久代:即方法区,某些情况会被回收

ZGC的3个stw

ZGC的stw时间只和Roots有关系,而G1的转移和存活大小有关系

  • 初始标记
  • 再标记
  • 初始转移

ZGC的着色指针

ZGC实际仅使用64位地址空间的第041位,而第4245位存储元数据,第47~63位固定为0。

practice

Go

CPU profile

profile可以剖析出CPU时间被花费在了哪些函数,一般runtime.gc消耗的占比不应该大于20%,否则可以考虑调优

pprof trace

相比profile,可以更细致地看到gc的执行路径

GODEBUG=gctrace=1

Java

参数

  • -Xmx:最大堆
  • Xms:最小堆
  • -XX:ReservedCodeCacheSize:JIT缓存
  • -XX:ConcGCThreads:并发回收垃圾的线程,默认是总核数的12.5%
  • -XX:ParallelGCThreads:STW阶段使用线程数,默认是总核数的60%
  • -XX:ZAllocationSpikeTolerance:ZGC触发自适应算法的修正系数,默认2,数值越大,越早的触发ZGC
  • -Xlog:设置GC日志中的内容、格式、位置以及每个日志的大小

ref

[译]Go 垃圾回收指南

Go GC 20 问

新一代垃圾回收器ZGC的探索与实践