GC压垮了最后一根稻草

long time no see,:), 各种原因下导致了这么久没更新,准备再次捡起来了,先分享下最近碰到的一个case。

这个case的现象是:
整个集群fgc严重,导致无法为用户提供服务。

原因分析:
从现象来看,应该是个相当简单的类似内存泄露的问题,模式化的开始了OOM问题排查的过程。

拿到fgc严重时的heap dump进行分析,heap dump显示确实java heap基本被用满了,继续看Dominator tree,惊讶的发现排第一的才占了7%的内存;
切换到leakage的视图看,发现有一类型的类占据了超过40%的内存,用GC root跟是什么地方在引用这些类,看到的是很多的http线程在持有这些类的引用;
为什么这么多的http线程持有这些类的引用又不释放呢,需要看下这些线程都在做什么,于是切换到thread视图,发现这些http线程中除了一个在run,其他的都在等一把锁,run的那个线程持有了这把锁,继续看run的那个线程那行代码在做什么,发现那是个极为简单的动作,完全不可能运行很久,这样的现象说明是由于某些原因导致这个run的线程一直获取不到cpu资源;
翻查当时机器的状况,系统的状况是正常的,但Java应用一直在fgc,连续频繁的fgc导致了上面的那些线程压根就没有执行的机会,在这种情况下这个应用其实已经不可能恢复了。

上面分析了那么多,唯一能得到的结论就是应用本身没有内存泄露,但到底是什么原因触发了应用频繁fgc呢,继续翻查应用的gc log,发现有个现象是在频繁fgc之前cms gc的频率越来越近,并且每次回收后存活的空间占用的越来越大,造成这样的现象是应用的qps增高了太多,或者reponse time(rt)慢了很多造成的,查看应用的qps没什么变化,但应用的rt在cms gc频率变短前有些许的增长,这样基本能解释通:
应用rt增高,导致在cms gc执行的周期内存活的对象增多,于是cms gc频率开始缩短;
而cms gc频率缩短,又进一步导致了rt增高,开始形成了恶性循环。
因此说明这个应用在目前的机器数和qps下其实已经到达瓶颈了,稍微的rt或qps的波动都有可能导致整集群崩盘。

在这个结的情况下问题是没法解决的,要解决这个问题只能是提升应用的性能或扩容机器。

case带来的思考:
这种case其实挺折腾的,在以前讲线程池大小的帖子里也写过一次,就是线程池的最大值开的太大的话,看起来是能接更多的请求(如果cpu处理不是瓶颈的话),但不幸的是通常也会导致存活的对象增多,有可能触发gc,而gc的频繁介入很容易导致恶化的现象。

要预防这种case的另外一个更好的做法还是保持一定频率压测获得应用的单机qps能力,并做相应的限流,确保整体qps达到一定的水位后就扩容。

=============================
欢迎关注微信公众号:hellojavacases

关于此微信号:
分享Java问题排查的Case、Java业界的动态和新技术、Java的一些小知识点Test,以及和大家一起讨论一些Java问题或场景,这里只有Java细节的分享,没有大道理、大架构和大框架。

公众号上发布的消息都存放在http://hellojava.info上。