上周排查了一个线上问题,主要现象是CPU占用太高,jvm old区占用太高,同时频繁fgc,我简单排查了下就草草收场了,可是事后我对这个问题又进行了复查,发现问题没有那么简单,下面跟着我一块儿分析一下究竟是怎么回事?java
必定要先读完上篇文章cpu使用率太高和jvm old占用太高排查过程spring
过后再看dump文件注意到最大的对象是一个ArrayList,里面几乎都是ElasticSearchStatusException对象apache
但是发生这个异常的操做上次已经被我定位到了,数据漏斗只有产品、运营等内部人员使用,经过使用频率推测,不该该有那么多对象。我猜测是否是代码中存在死循环,但没有找到。没办法只能在测试环境进行场景复现了。数组
经过上次排查到是es查询了不存在的索引致使异常,因此就把查询es的索引写死一个不存在的,最初尝试写个单测,但一直不能复现问题,因此只好部署到测试环境,在本地经过远程debug 调试程序jvm
远程debug到断点时,发现源码对不上工具
而后发现有可选择的源码,这里是关键测试
从org.apache.commons.lang:2.5jar包切换到springsource.org.apache.commons.lang:2.1.0包后,居然可以和测试环境对得上,但是代码中明明引用的commons.lang:2.5的包,这里说明在项目中类加载的时候,ExceptionUtils这个class文件并非从commons.lang中加载的,而是从springsource包中加载的 关于类文件加载的问题咱们先放到后面,先找代码的问题debug
看到这里,眼尖的朋友应该已经发现了bug的所在,那么恭喜你。若是没发现的朋友,不要着急,跟我一步一步来。咱们继续debug跟进代码的 getCause方法,能够看到经过遍历异常名字的数组验证是否在抛出的异常中存在3d
这些异常方法中的getRootCause方法,存在ElasticSearchStatusException的父类ElasticsearchException中调试
咱们看下这个方法,主要找最根本的异常缘由,有则返回,没有就返回当前的异常
继续跟代码,cause不为null,返回这个异常
bug代码定位
这个getThrowables方法,里面有个while循环,判断条件只进行了非空判断,不为null就添加到list中,注意观察我截图的时刻,list的大小 8万多,其实远远不止会看开头dump文件的大对象,是一个ArrayList,里面有大量的ElasticSearchStatusException对象
其实到这里已经定位到了FGC的真凶,判断条件没有排除返回的异常是已经添加到list中的异常,因此会一直循环添加,形成堆内存占用满了,FGC回收不掉这些对象,由于ArrayList一直持有他们的引用
正确代码应该以下面这样,因此开源工具库也是会有bug的,用的时候多加注意
public static Throwable[] getThrowables(Throwable throwable) { List list = new ArrayList(); // 这里的判断条件应该加上 list.contains(throwable) == false while (throwable != null && list.contains(throwable) == false) { list.add(throwable); throwable = ExceptionUtils.getCause(throwable); } return (Throwable[]) list.toArray(new Throwable[list.size()]); }
原本咱们就没想用springsource的方法,只是类加载的时候加载错了,那看下commons.lang包下的方法是否正确呢?能够看到这个包的方法是正确的,考虑到了这个问题
springsource的commons.lang包在2.2版本已经修复了这个问题 jar包最好引用最新的
上面咱们留了一个jvm加载class文件的问题,咱们知道jvm加载class的时候,若是存在包名和类名彻底同样,先加载一个后,另外的就不会再被加载了。
经查看两个ExceptionUtils确实包名类名彻底一致
其实经过前面的debug和代码分析,已经能肯定项目加载的ExceptionUtils.class文件来自springsource包,但仍是想经过必定手段验证一下。
其实jvm提供类相似的功能参数,修改项目启动脚本,添加jvm参数 -XX:+TraceClassLoading 而后重启项目并继让异常重现【这里要让触发异常重现,是由于是运行时异常】,并查看日志,查看ExceptionUtils.class的加载信息
能够看到确实和咱们推测的同样
其实这里还能够深刻研究jvm的类加载机制,类加载器加载顺序,双亲委派模型等
经过 mvn dependency:tree 查看jar包依赖状况,排除掉不用的jar包
到这里这个问题的排查应该告一段落了,从排查过程当中学到了很多,场景复现,梳理思路,用文章分享出来虽然说码字不易很耗时,但这是对本身的一种总结,里面仍是有不少乐趣与收获的。
后续会继续分享一些和广告系统相关的文章,敬请期待!
欢迎关注公众号 【天天晒白牙】,获取最新文章,咱们一块儿交流,共同进步!