Java教程:9种常见的CMS GC问题详解(四)

开课吧开课吧锤锤2021-03-22 10:16

    Java编程语言是一种简单、面向对象、分布式、解释型、健壮安全、与系统无关、可移植、高性能、多线程和动态的语言。如今Java已经广泛应用于各个领域的编程开发。

java

    3.2判断是不是GC引发的问题?

    到底是结果(现象)还是原因,在一次GC问题处理的过程中,如何判断是GC导致的故障,还是系统本身引发GC问题。这里继续拿在本文开头提到的一个Case:“GC耗时增大、线程Block增多、慢查询增多、CPU负载高等四个表象,如何判断哪个是根因?”,笔者这里根据自己的经验大致整理了四种判断方法供参考:

    时序分析:先发生的事件是根因的概率更大,通过监控手段分析各个指标的异常时间点,还原事件时间线,如先观察到CPU负载高(要有足够的时间Gap),那么整个问题影响链就可能是:CPU负载高->慢查询增多->GC耗时增大->线程Block增多->RT上涨。

    概率分析:使用统计概率学,结合历史问题的经验进行推断,由近到远按类型分析,如过往慢查的问题比较多,那么整个问题影响链就可能是:慢查询增多->GC耗时增大->CPU负载高->线程Block增多->RT上涨。

    实验分析:通过故障演练等方式对问题现场进行模拟,触发其中部分条件(一个或多个),观察是否会发生问题,如只触发线程Block就会发生问题,那么整个问题影响链就可能是:线程Block增多->CPU负载高->慢查询增多->GC耗时增大->RT上涨。

    反证分析:对其中某一表象进行反证分析,即判断表象的发不发生跟结果是否有相关性,例如我们从整个集群的角度观察到某些节点慢查和CPU都正常,但也出了问题,那么整个问题影响链就可能是:GC耗时增大->线程Block增多->RT上涨。

    不同的根因,后续的分析方法是完全不同的。如果是CPU负载高那可能需要用火焰图看下热点、如果是慢查询增多那可能需要看下DB情况、如果是线程Block引起那可能需要看下锁竞争的情况,最后如果各个表象证明都没有问题,那可能GC确实存在问题,可以继续分析GC问题了。

    3.3问题分类导读

    3.3.1Mutator类型

    Mutator的类型根据对象存活时间比例图来看主要分为两种,在弱分代假说中也提到类似的说法,如下图所示“SurvivalTime”表示对象存活时间,“Rate”表示对象分配比例:

    IO交互型:互联网上目前大部分的服务都属于该类型,例如分布式RPC、MQ、HTTP网关服务等,对内存要求并不大,大部分对象在TP9999的时间内都会死亡,Young区越大越好。

    MEM计算型:主要是分布式数据计算Hadoop,分布式存储HBase、Cassandra,自建的分布式缓存等,对内存要求高,对象存活时间长,Old区越大越好。

    当然,除了二者之外还有介于两者之间的场景,本篇文章主要讨论第一种情况。对象SurvivalTime分布图,对我们设置GC参数有着非常重要的指导意义,如下图就可以简单推算分代的边界。

Java

    3.3.2GC问题分类

    笔者选取了九种不同类型的GC问题,覆盖了大部分场景,如果有更好的场景,欢迎在评论区给出。

    UnexpectedGC:意外发生的GC,实际上不需要发生,我们可以通过一些手段去避免。

    SpaceShock:空间震荡问题,参见“场景一:动态扩容引起的空间震荡”。

    ExplicitGC:显示执行GC问题,参见“场景二:显式GC的去与留”。

    PartialGC:部分收集操作的GC,只对某些分代/分区进行回收。

    YoungGC:分代收集里面的Young区收集动作,也可以叫做MinorGC。

    ParNew:YoungGC频繁,参见“场景四:过早晋升”。

    OldGC:分代收集里面的Old区收集动作,也可以叫做MajorGC,有些也会叫做FullGC,但其实这种叫法是不规范的,在CMS发生ForegroundGC时才是FullGC,CMSScavengeBeforeRemark参数也只是在Remark前触发一次YoungGC。

    CMS:OldGC频繁,参见“场景五:CMSOldGC频繁”。

    CMS:OldGC不频繁但单次耗时大,参见“场景六:单次CMSOldGC耗时长”。

    FullGC:全量收集的GC,对整个堆进行回收,STW时间会比较长,一旦发生,影响较大,也可以叫做MajorGC,参见“场景七:内存碎片&收集器退化”。

    MetaSpace:元空间回收引发问题,参见“场景三:MetaSpace区OOM”。

    DirectMemory:直接内存(也可以称作为堆外内存)回收引发问题,参见“场景八:堆外内存OOM”。

    JNI:本地Native方法引发问题,参见“场景九:JNI引发的GC问题”。

    3.3.3排查难度

    一个问题的解决难度跟它的常见程度成反比,大部分我们都可以通过各种搜索引擎找到类似的问题,然后用同样的手段尝试去解决。当一个问题在各种网站上都找不到相似的问题时,那么可能会有两种情况,一种这不是一个问题,另一种就是遇到一个隐藏比较深的问题,遇到这种问题可能就要深入到源码级别去调试了。以下GC问题场景,排查难度从上到下依次递增。

以上内容由开课吧老师、新宇、湘铭、祥璞提供,更多Java教程尽在开课吧广场Java教程频道。

有用
分享