背景
公司线上有个tomcat服务,里面合并部署了大概8个微服务,之所以没有像其他微服务那样单独部署,其目的是为了节约服务器资源,况且这8个服务是属于边缘服务,并发不高,就算宕机也不会影响核心业务。
因为并发不高,所以线上一共部署了2个tomcat进行负载均衡。
这个tomcat刚上生产线,运行挺平稳。大概过了大概1天后,运维同事反映2个tomcat节点均挂了。无法接受新的请求了。CPU飙升到100%。
排查过程一
接手这个问题后。首先大致看了下当时的JVM监控。
CPU的确居高不下
FULL GC从大概这个小时的22分开始,就开始频繁的进行FULL GC,一分钟最高能进行10次FULL GC
minor GC每分钟竟然接近60次,相当于每秒钟都有minor GC
从老年代的使用情况也反应了这一点
随机对线上应用分析了线程的cpu占用情况,用top -H -p pid命令
可以看到前本文来源gao.dai.ma.com搞@代*码(网$面4条线程,都占用了大量的CPU资源。随即进行了jstack,把线程栈信息拉下来,用前面4条线程的ID转换16进制后进行搜索。发现并没有找到相应的线程。所以判断为不是应用线程导致的。
第一个结论
通过对当时JVM的的监控情况,可以发现。这个小时的22分之前,系统 一直保持着一个比较稳定的运行状态,堆的使用率不高,但是22分之后,年轻代大量的minor gc后,老年代在几分钟之内被快速的填满。导致了FULL GC。同时FULL GC不停的发生,导致了大量的STW,CPU被FULL GC线程占据,出现CPU飙高,应用线程由于STW再加上CPU过高,大量线程被阻塞。同时新的请求又不停的进来,最终tomcat的线程池被占满,再也无法响应新的请求了。这个雪球终于还是滚大了。
分析完了案发现场。要解决的问题变成了:
是什么原因导致老年代被快速的填满?
拉了下当时的JVM参数
-Djava.awt.headless=true -Dfile.encoding=UTF-8 -server -Xms2048m -Xmx4096m -Xmn2048m -Xss512k -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -XX:+DisableExx
plicitGC -XX:MaxTenuringThreshold=5 -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly -XX:+UseCMSCompactAtFullCollection
-XX:+PrintGCDetails -Xloggc:/data/logs/gc.log
总共4个G的堆,年轻代单独给了2个G,按照比率算,JVM内存各个区的分配情况如下:
所以开始怀疑是JVM参数设置的有问题导致的老年代被快速的占满。
但是其实这参数已经是之前优化后的结果了,eden区设置的挺大,大部分我们的方法产生的对象都是朝生夕死的对象,应该大部分都在年轻代会清理了。存活的对象才会进入survivor区。到达年龄或者触发了进入老年代的条件后才会进入老年代。基本上老年代里的对象大部分应该是一直存活的对象。比如static修饰的对象啊,一直被引用的 缓存啊,spring容器中的bean等等。