1.现象
在某个阳光明媚的中午(也有一说是下午),王童鞋急冲冲的跑过来,反馈了一个问题,就是每次点下页面的某个查询按钮,总是固定的等待很久的时间服务器没相应,而且其他功能还伴随间歇性的有卡顿现象。他反馈说,有可能是Mysql的问题导致的。并且还把监控的截图发给笔者看了下:
当然,这里显示的是Mysql的获取连接的时间,一直在递增,到了30号,基本上一次需要快2秒钟了。
OK,于是第一时间去阿里云RDS上面查看了数据库使用情况。
-
服务器支持的连接数情况:使用率才用10%不到
-
这个是数据库的机器指标,基本指标都很平稳,值都不是很高
全部指标翻了一遍,没发现什么问题,并且看起来数据库都没啥压力。看来这个有可能不是数据库问题, 这个Mysql获取连接慢,也许是现象,而不是原因。
2.现场排查
2.1 寻找线索
话说回来,每次排查线上问题的时候,都像破案一样,一方面需要保留现场痕迹,另一方面也需要寻找其他的各种蛛丝马迹。所以这里一边让王童鞋把JVM的堆栈等信息给dump出来(当然,为了不影响线上环境使用,数据dump出来之后,王童鞋也重启了机器),笔者一边继续翻日志&监控。
当查看了JVM信息之后,元凶的尾巴被揪出来了一些:
Pause Durations表示的是暂停时间,也就是JVM里面的STW(Stop The World)时间。图表里面avg/max end of minor GC (G1 Evacuation Pause)这个代表的是G1里面的young-gc。此刻, 我们知道G1里面的YGC有问题,但是还不够,因为我们需要知道具体问题点在哪呢?又什么原因导致的呢?
2.2 G1的young-gc简介
2.2.1 简介
作为CMS替代品的G1,一直吸引着众多Java开发者的目光。G1的目标是在满足期望的停顿时间的同时保存高吞吐量,适用于多核、大内存的系统。
在分配内存的时候,如果剩余的空间不能满足要分配的对象的时候,就会优先触发新生代回收,即young-gc(YGC)。G1中的YGC触发时,会一次性回收全部的新生代内存以及空的大对象区(前提是分区没有RSet引用)。目前比较新的垃圾收集器例如ZGC、Shenandoah都被称之为低延迟垃圾收集器,其目标是在任意内存下,保证停顿时间都保持在十毫秒以内。可见低延迟在垃圾收集器中的重要性。但是在G1中的YGC,它全部过程都是处于STW的,也就是说YGC的过程中,服务器是处于停顿状态中的。这也就解释了为什么Mysql获取连接超时他其实是现象而不是原因,因为不单单是Mysql获取连接, 在YGC的时候,除了 GC 线程之外整个服务器的所有Mutator线程( Java 应用线程)都停止了 ,服务卡顿便如此由来。
现在,通过监控知道了服务器慢,是因为YGC导致的,但是YGC的过程比较多,具体慢在哪,还是不得而知,问题如果想解决,还需要继续排查。
2.2.2 YGC过程图解
首先,在这里画一张图讲下G1的YGC的过程:
通过上面的图例也看到了,一次YGC过程还是有挺多操作的,那么YGC具体慢在哪些点呢?单凭监控还是不能找到元凶的,接下来还需要一些更有力的信息才能继续调查下去,这就是前面Dump出来的堆内存以及服务器中打印的GC日志。
2.3 重要线索
2.3.1 GC日志
接下来切到机器中,查看最近几次的日志。
总共花了0.4573432秒,共8核并行执行。仔细,看这些数据,发现有一处很不合理,就是上面红色的那一行。 Ext Root Scanning堆外部根扫描,总共花了596ms,其中最高448ms ,要知道这次ygc也才花457ms,几乎把所有时间都花在这个Ext Root Scanning。
2.3.2 堆分析
下面是当时dump出来的堆里面发现的不正常对象:
这里可看到堆里面这个CompositeClassLoader这个类的对象偏多。要知道类加载器在系统中负责对类进行加载,通过传入一个类的全限定名称返回一个唯一的确定的类实例。这实属不正常。通过其包名字得知是xstream下面的(xstream是用于处理XML文件的序列化以及反序列化等操作)。
于是查阅xstream的主要的类 com.thoughtworks.xstream.XStream#XStream源码:
public XStream(ReflectionProvider reflectionProvider, Mapper mapper, HierarchicalStreamDriver driver) {
this(reflectionProvider, driver, new CompositeClassLoader(), mapper);
}
这里是服务里面创建的代码:
public static XStream createNew() {
XStream xStream = new XStream(new Xpp3Driver(new NoNameCoder())) {
@Override
protected MapperWrapper wrapMapper(MapperWrapper next) {
return new MapperWrapper(next) {
....省去一些无用的代码
}
};
}
};
...省去一些无用的代码