




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、今咱们来聊聊JVM堆外内存泄露的BUG是如何查找的前言JVM的堆外内存泄露的定位一直是个比较棘手的问题。此次的Bug查找从堆内内存的泄露反推由堆外内存,同时对物理内存的使用做了定量的分析,从而实锤了Bug的源头。笔者将此Bug分析的过程写成博客,以飨读者。由于物理内存定量分析部分用到了linuxkernel虚拟内存管理的知识,读者如果有兴趣了解请看ulk3(深入理解linux内核第三版)内存泄露Bug现场一个线上稳定运行了三年的系统,从物理机迁移到docker环境后,运行了一段时间,突然被监控系统发生了莫些实例不可用的报警。所幸有负载均衡,可以自动下掉节点,如下图所示:登录到对应机器上后,发现
2、由于内存占用太大,触发OOM,然后被linux系统本身给kill了。应急措施紧急在由问题的实例上再次启动应用,启动后,内存占用正常,一切Okay。奇怪现象当前设置的最大堆内存是1792M,如下所示:-Xmx1792m-Xms1792m-Xmn900m-XX:PermSize=256m-XX:MaxPermSize=256m-server-Xss512k查看操作系统层面的监控,发现内存占用情况如下图所示:上图蓝色的线表示总的内存使用量,发现一直涨到了4G后,超生了系统限制。很明显,有堆外内存泄露了。推荐一个交流学习群:478030634里面会分享一些资深架构师录制的视频录像:有Spring,My
3、Batis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多:查找线索gc日志一般由现内存泄露,笔者立马想到的就是查看当时的gc日志。本身应用所采用框架会定时打印由对应的gc日志,遂查看,发现gc日志一切正常。对应日志如下:查看了当天的所有gc日志,发现内存始终会回落到170M左右,并无明显的增加。要知道JVM进程本身占用的内存可是接近4G(加上其它进程,例如日志进程就已经到4G了),进一步确认是堆外内存导致。排查代码打开线上服务对应对应代码,查了一圈,发现没有任何地方显式利用堆外内存,其没有依赖任何
4、额外的native方法。关于网络IO的代码也是托管给Tomcat,很明显,作为一个全世界广泛流行的Web服务器,Tomcat不大可能有堆外内存泄露。进一步查找由于在代码层面没有发现堆外内存的痕迹,那就继续找些其它的信思,希望能发现蛛丝马迹。Dump出JVM的Heap堆由于线上由问题的Server已经被kill,还好有其它几台,登上去发现它们也占用了很大的堆外内存,只是还没有到触发OOM的临界点而已。于是就赶紧用jmapdump了两台机器中应用JVM的堆情况,这两台留做现场保留不动,然后将其它机器迅速重启,以防同时被OOM导致服务不可用。使用如下命令dump:jmap-dump:format=b
5、,file=heap.binpid使用MAT分析Heap文件挑了一个heap文件进行分析,堆的使用情况如下图所示:一共用了200多M,和之前gc文件打印由来的170M相差不大,远远没有到4G的程度。不得不说MAT是个非常好用的工具,它可以提示你可能内存泄露的点:这个cachedBnsClient类有12452个实例,占用了整个堆的61.92%。查看了另一个heap文件,发现也是同样的情况。这个地方肯定有内存泄露,但是也占用了130多M,和4G相差甚远。查看对应的代码系统中大部分对于CachedBnsClient的调用,都是通过注解Autowired的,这部分实例数很少。唯一频繁产生此类实例的代
6、码如下所示:Overridepublicvoidfun()BnsClientbnsClient=newCachedBnsClient。;/dosomethingreturn;止匕CachedBnsClient仅仅在方法体内使用,并没有逃逸到外面,再看此类本身publicclassCachedBnsClientprivateConcurrentHashMap>authCache=newConcurrentHashMap>();privateConcurrentHashMap>validUriCache=newConcurrentHashMap>();privateConc
7、urrentHashMap>uriCache=newConcurrentHashMap>();没有任何static变量,同时也没有往任何全局变量注册自身。换言之,在类的成员(Member)中,是不可能由现内存泄露的当时只粗略的过了一过成员变量,回过头来细想,还是漏了不少地方的。更多信息由于代码排查下来,感觉这块不应该由现内存泄露(但是事实确是如此的打脸)。这个类也没有显式用到堆外内存,而且只占了130M,和4G比起来微不足道,还是先去追查主要矛盾再说。使用jstackdump线程信息现场信息越多,越能找由蛛丝马迹。先用jstack把线程信息dump下来看下。这一看,立马发现了不同,
8、除了正常的IO线程以及框架本身的一些守护线程外,竟然还多由来了12563多个线程。'Thread-5'daemonprio=10tid=0x00007fb79426e000nid=0x7346waitingoncondition0x00007fb7b5678000java.lang.Thread.State:TIMED_WAITING(sleeping)atjava.lang.Thread.sleep(NativeMethod)atcom.xxxxx.CachedBnsClient$1.run(CachedBnsClient.java:62)而且这些正好是运行再CachedBn
9、sClient的run方法上面!这些特定线程的数量正好是12452个,和cachedBnsClient数量一致!再次check对应代码原来刚才看CachedBnsClient代码的时候遗漏掉了一个关键的点!publicCachedBnsClient(BnsClientclient)super();this.backendClient=client;newThread()Overridepublicvoidrun()for(;)refreshCache();tryThread.sleep(60*1000);catch(InterruptedExceptione)logger.error('
10、;由错',e);这段代码是CachedBnsClient的构造函数,其在里面创建了一个无限循环的线程,每隔60s启动一次刷新一下里面的缓存!找到关键点在看到12452个等待在CachedBnsClient.run的业务的一瞬间笔者就意识到,肯定是这边的线程导致对外内存泄露了。下面就是根据线程大小计算其泄露内存量是不是确实能够引起OOM了。发现内存计算对不上由于我们这边设置的Xss是512K,即一个线程栈大小是512K,而由于线程共享其它MM单元(线程本地内存是是现在线程栈上的),所以实际线程堆外内存占用数量也是512K。进行如下计算:12563*512K=6331M=6.3G整个环境一
11、共4G,加上JVM堆内存1.8G(1792M),已经明显的超过了4G。(6.3G1.8G)=8.1G>4G如果按照此计算,应用应用早就被OOM了。怎么回事呢?为了解决这个问题,笔者又思考了好久。如下所示:Java线程底层实现JVM的线程在linux上底层是调用NPTL(NativePosixThreadLibrary)来创建的,一个JVM线程就对应linux的lwp(轻量级进程,也是进程,只不过共享了mm_struct,用来实现线程),一个thread.start就相当于do_fork了一把。其中,我们在JVM启动时候设置了-Xss=512K(即线程栈大小),这512K中然后有8K是必须
12、使用的,这8K是由进程的内核栈和thread_info公用的,放在两块连续的物理页框上。如下图所示:众所周知,一个进程(包括lwp)包括内核栈和用户栈,内核栈thread_inf。用了8K,那么用户态的栈可用内存就是:512K-8K=504K如下图所示:Linux实际物理内存映射事实上linux对物理内存的使用非常的抠门,一开始只是分配了虚拟内存的线性区,并没有分配实际的物理内存,只有推到最后使用的时候才分配具体的物理内存,即所谓的请求调页。如下图所示:查看smaps进程内存使用信息使用如下命令,查看cat/proc/pid/smaps>smaps.txt实际物理内存使用信息,如下所示:
13、7fa69a6d1000-7fa69a74f000rwxp0000000000:000Size:504kBRss:92kBPss:92kBShared_Clean:0kBShared_Dirty:0kBPrivate_Clean:0kBPrivate_Dirty:92kBReferenced:92kBAnonymous:92kBAnonHugePages:0kBSwap:0kBKernelPageSize:4kBMMUPageSize:4kB7fa69a7d3000-7fa69a851000rwxp0000000000:000Size:504kBRss:152kBPss:152kBShared
14、_Clean:0kBShared_Dirty:0kBPrivate_Clean:0kBPrivate_Dirty:152kBReferenced:152kBAnonymous:152kBAnonHugePages:0kBSwap:0kBKernelPageSize:4kBMMUPageSize:4kB搜索下504KB,正好是12563个,对了12563个线程,其中Rss表示实际物理内存(含共享库)92KB,Pss表示实际物理内存(按比例共享库)92KB(由于没有共享库,所以Rss=Pss),以第一个7fa69a6d1000-7fa69a74f000线性区来看,其映射了92KB的空间,第二个映射
15、了152KB的空间。如下图所示:挑由符合条件(即size是504K)的几十组看了下,基本都在92K-152K之间,再加上内核栈8K(92152)/28K=130K,由于是估算,取整为128K,即反映此应用平均线程栈大小。注意,实际内存有波动的原因是由于环境不同,从而走了不同的分支,导致栈上的增长不同。重新进行内存计算JVM一开始申请了-Xmx1792m-Xms1792m即1.8G的堆内内存,这里是即时分配,一开始就用物理页框填充。12563个线程,每个线程栈平均大小128K,即:128K*12563=1570M=1.5G的对外内存取个整数128K,就能反映由平均水平。再拿这个128K*1256
16、3=1570M=1.5G,加上JVM的1.8G,就已经达到了3.3G,再加上kernel和日志传输进程等使用的内存数量,确实已经接近了4G,这样内存就对应上了!(注:用于定量内存计算的环境是一台内存用量将近4G,但还没OOM的机器)为什么在物理机上没有应用Down机笔者登录了原来物理机,应用还在跑,发现其同样有堆外内存泄露的现象,其物理内存使用已经达到了5个多G!幸好物理机内存很大,而且此应用发布还比较频繁,所以没有被OOMoDump了物理机上应用的线程,一共有28737个线程,其中28626个线程等待在CachedBnsClient上。同样用smaps查看进程实际内存信息,其平均大小依旧为128K,因为是同一应用的原因继续进行物理内存计算1.8(28737*128k)/1024K=(3.61.8)=5.4G进一步验证了我们的推理。这么多线程应用为什么没有卡顿因为基本所有的线程都睡眠在Thread.sleep(6
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 中国移动东营市2025秋招心理测评常考题型与答题技巧
- 六盘水市中石化2025秋招写作申论万能模板直接套用
- 中国移动大兴安岭地区2025秋招面试无领导高频议题20例
- 安康市中石油2025秋招面试半结构化模拟题及答案机械与动力工程岗
- 邵阳市中石油2025秋招面试半结构化模拟题及答案法律与合规岗
- 新疆地区中石油2025秋招笔试模拟题含答案机械与动力工程岗
- 那曲市中石化2025秋招写作申论万能模板直接套用
- 中国联通甘孜自治州2025秋招技术岗专业追问清单及参考回答
- 汕尾市中储粮2025秋招面试专业追问题库安全环保岗
- 中国广电白银市2025秋招写作案例分析万能模板直接套用
- 幼儿园膳食委员会模板下载
- 脑血管介入围手术期护理
- 2025年江苏二级造价工程师考试《建设工程造价管理基础知识》真题(含答案)
- 数字人文视域下的文化圈重构-洞察阐释
- 爱心义卖班会课课件
- 化验员职业技能培训考试题库及答案(含各题型)
- 2025年广东省中考历史试题卷(含答案详解)
- 阴挺的中医护理
- 中药熏洗法试题及答案
- 施工现场环境保护管理标准化图册
- 2023梅毒螺旋体血清学试验生物学假阳性处理专家共识
评论
0/150
提交评论