线上服务的 GC 问题排查,看这篇就够了( 二 )


这里,我选择几个最核心的知识点,展开介绍下GC的运行原理,最后再给出一份实践指南 。
1. 堆内存结构
大家都知道: GC分为YGC和FGC,它们均发生在JVM的堆内存上 。先来看下JDK8的堆内存结构:

线上服务的 GC 问题排查,看这篇就够了

文章插图
 
可以看到,堆内存采用了分代结构,包括新生代和老年代 。新生代又分为:Eden区,From Survivor区(简称S0),To Survivor区(简称S1区),三者的默认比例为8:1:1 。另外,新生代和老年代的默认比例为1:2 。
堆内存之所以采用分代结构,是考虑到绝大部分对象都是短生命周期的,这样不同生命周期的对象可放在不同的区域中,然后针对新生代和老年代采用不同的垃圾回收算法,从而使得GC效率最高 。
2. YGC是什么时候触发的?
大多数情况下,对象直接在年轻代中的Eden区进行分配,如果Eden区域没有足够的空间,那么就会触发YGC(Minor GC),YGC处理的区域只有新生代 。因为大部分对象在短时间内都是可收回掉的,因此YGC后只有极少数的对象能存活下来,而被移动到S0区(采用的是复制算法) 。
当触发下一次YGC时,会将Eden区和S0区的存活对象移动到S1区,同时清空Eden区和S0区 。当再次触发YGC时,这时候处理的区域就变成了Eden区和S1区(即S0和S1进行角色交换) 。每经过一次YGC,存活对象的年龄就会加1 。
3. FGC又是什么时候触发的?
下面4种情况,对象会进入到老年代中:
  • YGC时,To Survivor区不足以存放存活的对象,对象会直接进入到老年代 。
  • 经过多次YGC后,如果存活对象的年龄达到了设定阈值,则会晋升到老年代中 。
  • 动态年龄判定规则,To Survivor区中相同年龄的对象,如果其大小之和占到了 To Survivor区一半以上的空间,那么大于此年龄的对象会直接进入老年代,而不需要达到默认的分代年龄 。
  • 大对象:由-XX:PretenureSizeThreshold启动参数控制,若对象大小大于此值,就会绕过新生代, 直接在老年代中分配 。
当晋升到老年代的对象大于了老年代的剩余空间时,就会触发FGC(Major GC),FGC处理的区域同时包括新生代和老年代 。除此之外,还有以下4种情况也会触发FGC:
  • 老年代的内存使用率达到了一定阈值(可通过参数调整),直接触发FGC 。
  • 空间分配担保:在YGC之前,会先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间 。如果小于,说明YGC是不安全的,则会查看参数 HandlePromotionFailure 是否被设置成了允许担保失败,如果不允许则直接触发Full GC;如果允许,那么会进一步检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果小于也会触发 Full GC 。
  • Metaspace(元空间)在空间不足时会进行扩容,当扩容到了-XX:MetaspaceSize 参数的指定值时,也会触发FGC 。
  • System.gc() 或者Runtime.gc() 被显式调用时,触发FGC 。
4. 在什么情况下,GC会对程序产生影响?
不管YGC还是FGC,都会造成一定程度的程序卡顿(即Stop The World问题:GC线程开始工作,其他工作线程被挂起),即使采用ParNew、CMS或者G1这些更先进的垃圾回收算法,也只是在减少卡顿时间,而并不能完全消除卡顿 。
那到底什么情况下,GC会对程序产生影响呢?根据严重程度从高到底,我认为包括以下4种情况:
  • FGC过于频繁:FGC通常是比较慢的,少则几百毫秒,多则几秒,正常情况FGC每隔几个小时甚至几天才执行一次,对系统的影响还能接受 。但是,一旦出现FGC频繁(比如几十分钟就会执行一次),这种肯定是存在问题的,它会导致工作线程频繁被停止,让系统看起来一直有卡顿现象,也会使得程序的整体性能变差 。
  • YGC耗时过长:一般来说,YGC的总耗时在几十或者上百毫秒是比较正常的,虽然会引起系统卡顿几毫秒或者几十毫秒,这种情况几乎对用户无感知,对程序的影响可以忽略不计 。但是如果YGC耗时达到了1秒甚至几秒(都快赶上FGC的耗时了),那卡顿时间就会增大,加上YGC本身比较频繁,就会导致比较多的服务超时问题 。
  • FGC耗时过长:FGC耗时增加,卡顿时间也会随之增加,尤其对于高并发服务,可能导致FGC期间比较多的超时问题,可用性降低,这种也需要关注 。
  • YGC过于频繁:即使YGC不会引起服务超时,但是YGC过于频繁也会降低服务的整体性能,对于高并发服务也是需要关注的 。
其中,「FGC过于频繁」和「YGC耗时过长」,这两种情况属于比较典型的GC问题,大概率会对程序的服务质量产生影响 。剩余两种情况的严重程度低一些,但是对于高并发或者高可用的程序也需要关注 。


推荐阅读