年前最后一场技巧盛宴 | 1月27日与京东、日记易技巧大年夜咖畅聊智能化运维成长趋势!
媒介
JVM的堆外内存泄漏的定位一向是个比较棘手的问题。此次的Bug查找大年夜堆内内存的泄漏反推出堆外内存,同时对物理内存的应用做了定量的分析,大年夜而实锤了Bug的源徒辜笔者将此Bug分析的过程写成博客,以飨读者。
因为物理内存定量分析部分用到了linux kernel虚拟内存治理的常识,读者如不雅有兴趣懂得请看ulk3(《深刻懂得linux内核第三版》)
内存泄漏Bug现场
一个线上稳定运行了三年的体系,大年夜物理机迁徙到docker情况后,运行了一段时光,忽然被监控体系发出了某些实例弗采取的报警。所幸有负载均衡,可以主动下掉落节点,如下图所示:
登录到对应机械上后,发明因为内存占用太大年夜,触发OOM,然后被linux体系本身给kill了。
应急办法
当前设置的最大年夜堆内存是1792M,如下所示:
-Xmx1792m -Xms1792m -Xmn900m -XX:PermSize=256m -XX:MaxPermSize=256m -server -Xss512k
查看操作体系层面的监控,发明内存占用情况如下图所示:
上图蓝色的线表示总的内存应用量,发明一向涨到了4G后,超出了体系限制。
很明显,有堆外内存泄漏了。
查找线索
gc日记
一般出现内存泄漏,笔者立马想到的就是查看当时的gc日记。
本身应用所采取框架会准时打印出对应的gc日记,遂查看,发明gc日记一切正常。对应日记如下:
查看了当天的所有gc日记,发明内存始终会回落到170M阁下,并无明显的增长。要知道JVM过程本身占用的内存可是接近4G(加上其它过程,例如日记过程就已经到4G了),进一步确认是堆外内存导致。
排查代码
打开线上办事对应对应代码,查了一圈,发明没有任何处所显式应用堆外内存,其没有依附任何额外的native办法。关于收集IO的代码也是托管给Tomcat,很明显,作为一个全世界广泛风行的Web办事器,Tomcat不大年夜可能有堆外内存泄漏。
进一步查找
因为线上出问题的Server已经被kill,还好有其它几台,登上去发明它们也 占用了很大年夜的堆外内存,只是还没有到触发OOM的临界点罢了。于是就赶紧用jmap dump了两台机械中应用JVM的堆情况,这两台留做现场保存不动,然后将其它机械敏捷重启,以防同时被OOM导致办事弗采取。
应用如下敕令dump:
jmap -dump:format=b,file=heap.bin [pid]
应用MAT分析Heap文件
挑了一个heap文件进行分析,堆的应用情况如下图所示:
一共用了200多M,和之前gc文件打印出来的170M相差不大年夜,远远没有到4G的程度。
紧急在出问题的实例上再次启动应用,启动后,内存占用正常,一切Okay。
奇怪现象
不得不说MAT是个异常好用的对象,它可以提示你可能内存泄漏的点:
这个cachedBnsClient类有12452个实例,占用了全部堆的61.92%。
查看了另一个heap文件,发明也是同样的情况。这个处所肯定有内存泄漏,然则也占用了130多M,和4G相差甚远。
查看对应的代码
体系中大年夜部分对于CachedBnsClient的调用,都是经由过程注解Autowired的,这部分实例数很少。
独一频繁产生词攀类实例的代码如下所示:
@Override public void fun() { BnsClient bnsClient = new CachedBnsClient(); // do something return ;}
此CachedBnsClient仅仅在办法体内应用,并没有逃逸到外面,再看词攀类本身
没有任何static变量,同时也没有往任何全局变量注册自身。换言之,在类的成员(Member)中,是弗成能出现内存泄漏的。
当时只粗略的过了一过成员变量,回过火来细想,照样漏了不少处所的。
更多信息
现场信息越多,越能找出蛛丝马迹。先用jstack把线程信息dump下来看下。 这一看,立马发清楚明了不合,除了正常的IO线程以及框架本身的一些守护线程外,竟然还多出来了12563多个线程。
"Thread-5" daemon prio=10 tid=0x00007fb79426e000 nid=0x7346 waiting>再次check对应代码本来刚才看CachedBnsClient代码的时刻漏掉掉落了一个关键的点!
public CachedBnsClient(BnsClient client) { super(); this.backendClient = client; new Thread() { @Override public void run() { for (; ; ) { refreshCache(); try { Thread.sleep(60 * 1000); } catch (InterruptedException e) { logger.error("掉足", e); } } } }这段代码是CachedBnsClient的构造函数,其在琅绫擎创建了一个无穷轮回的线程,每隔60s启动一次刷新一下琅绫擎的缓存!
推荐阅读
年前最后一场技巧盛宴 | 1月27日与京东、日记易技巧大年夜咖畅聊智能化运维成长趋势! 问题描>>>详细阅读
本文标题:解Bug之路:记一次JVM堆外内存泄露Bug的查找
地址:http://www.17bianji.com/lsqh/40306.html
1/2 1