今咱们来聊聊JVM 堆外内存泄露的BUG是如何查找的
内存泄漏检测原理

内存泄漏检测原理
内存泄漏是指在程序运行时,由于程序中的某些代码未能正确释放已经分配的内存空间,导致系统中存在大量没有被使用的内存空间,从而导致系统性能下降、崩溃甚至瘫痪的现象。
为了避免内存泄漏对系统造成的影响,我们需要进行内存泄漏检测。
内存泄漏检测的原理是通过跟踪程序运行时的内存分配和释放
情况,检测出程序中存在的内存泄漏问题。
一般来说,内存泄漏检测器会在程序运行时记录下每次内存分配和释放的情况,并将它们保存在一个内存分配表中。
当程序结束时,内存泄漏检测器会比对内存分配表和内存释放表,如果发现有未被释放的内存,则会提示用户程序中存在内存泄漏问题。
在实际应用中,内存泄漏检测器可以通过静态分析和动态分析两种方式进行检测。
静态分析是指在编译阶段对程序进行分析,通过检查变量的生命周期和内存分配与释放的情况,来判断程序中是否存在内存泄漏问题。
动态分析则是在程序运行时对程序进行监控,实时监测内存分配和释放的情况,以及内存使用情况,来检测程序中是否存在内存泄漏问题。
总之,内存泄漏检测是保证程序运行稳定和性能优化的重要手段。
通过使用内存泄漏检测器,我们可以及时发现和解决程序中的内存泄漏问题,提高程序的稳定性和性能,从而提高用户的体验。
- 1 -。
内存泄漏检测原理

内存泄漏检测原理
内存泄漏是指程序在分配内存后,没有正确释放,导致程序运行
时内存占用量不断增加,直到系统无法再分配新的内存而崩溃。
因此,内存泄漏是程序开发过程中常见的问题。
为了解决内存泄漏问题,我们需要通过一些工具和技术进行检测。
其中一种常见的方法是使用内存泄漏检测工具。
大部分的内存泄漏检
测工具,都是通过在程序运行时动态的跟踪、记录和分析内存分配的
过程,从而找到内存泄漏的位置。
具体的原理如下:
1.内存泄漏检测工具会在程序运行时注入一些代码,用于跟踪内
存的分配和释放。
2.当程序分配内存时,内存泄漏检测工具会记录下分配的内存地
址和大小等信息。
3.当程序释放内存时,内存泄漏检测工具会将该内存地址标记为
可用。
4.当程序结束时,内存泄漏检测工具会扫描内存中所有未释放的
内存块。
5.如果发现存在未释放的内存块,则会输出相应的错误信息,并
指出该内存块的位置和大小等信息。
通过这种方式,内存泄漏检测工具可以在程序运行时检测到内存
泄漏的问题,帮助开发人员快速定位问题并进行修复。
为了确保程序
的性能和稳定性,开发人员应该尽可能的避免内存泄漏问题的发生,同时定期使用内存泄漏检测工具进行检测和修复。
内存泄漏的检测定位和解决经验总结

内存泄漏的检测定位和解决经验总结内存泄漏是指程序在运行过程中,分配的内存没有被正确释放,导致内存资源无法被再次利用的情况。
由于没有及时释放内存,内存泄漏会导致系统的内存消耗不断增加,最终可能造成程序崩溃或者系统运行缓慢。
解决内存泄漏问题需要进行检测、定位和解决。
一、内存泄漏的检测1. 使用内存分析工具:可以使用一些专门的内存分析工具来检测内存泄漏问题,例如Valgrind、Memcheck等。
这些工具可以跟踪程序运行过程中的内存分配和释放,帮助定位内存泄漏的位置。
2.编写测试用例:通过编写一些针对性的测试用例,模拟程序运行过程中常见的内存分配和释放场景,观察内存的使用情况。
如果发现内存占用持续增长或者没有被及时释放,就可以判断存在内存泄漏问题。
3.监控系统资源:通过监控系统的资源使用情况,如内存占用、CPU使用率等,可以观察系统是否存在内存泄漏的迹象。
如果发现系统的内存占用不断增加,并且没有明显的释放情况,就需要进一步检查是否存在内存泄漏。
二、内存泄漏的定位1.使用日志输出:通过在程序中添加日志输出语句,记录程序运行过程中的重要信息,特别是涉及内存分配和释放的地方。
通过观察日志输出,可以发现是否有内存没有被正确释放的情况。
2.代码分析:通过代码分析,找出可能导致内存泄漏的地方。
常见的内存泄漏问题包括:不恰当的内存分配和释放顺序、不正确的内存释放方式、内存分配大小不匹配等。
对于涉及动态分配内存的地方,要特别关注是否有被遗漏的释放操作。
3.堆栈跟踪:当发现内存泄漏问题比较复杂或者难以定位时,可以使用堆栈跟踪来追踪内存分配和释放的调用路径,找出内存泄漏的具体位置。
在调试过程中,可以通过打印调用栈来获取函数调用的过程,进而确定哪个函数没有正确释放内存。
三、内存泄漏的解决1.及时释放内存:在程序中,所有动态分配的内存都需要及时释放。
对于每个内存分配操作,都要确保相应的释放操作存在,并且在适当的时候进行调用。
java内存泄露排查思路

java内存泄露排查思路
Java内存泄露排查思路:
首先,我们需要确认是否存在内存泄露,并明确它的表现特征。
Java内存泄露主要表现在:反复发生OutOfMemoryError异常;性能下降;可用内存大小不断减少;JVM占用的系统内存不断增加。
接下来,我们就可以开始排查相关内存泄露问题了。
1.使用工具检查存在哪些内存泄露问题:Java提供各种工具来帮
助检测和确定是否存在内存泄漏,包括VisualVM、HeapWalker、jmap、jhat等。
2.查看内存分配和使用情况,看哪些对象使用了大量的内存:通
过VisualVM等工具查看内存使用情况,分析哪些对象占用了大量内存,从而确定存在内存泄漏的类。
3.分析内存泄漏的原因:分析存在内存泄漏的类,确定泄漏的原因。
可能的原因有:线程池配置不当;对象不受监控;未正确关闭JDBC资源等。
4.采取措施解决内存泄漏问题:根据内存泄漏的原因,采取措施
解决内存泄漏问题,如:定期回收无用线程;定期检查对象是否受到
监控;正确关闭JDBC资源等。
最后,在解决内存泄漏后,要定期测试程序,以确保解决方案的
正确性。
内存泄漏的检测定位和解决经验总结

内存泄漏的检测定位和解决经验总结内存泄漏是指在程序运行过程中,分配的内存一直没有被释放,导致内存的使用量越来越大,最终耗尽系统资源,造成程序崩溃。
内存泄漏是一种常见的程序缺陷,需要及时发现和解决。
一、检测内存泄漏的方法有以下几种:1. 静态代码检查:通过静态代码分析工具进行检查,工具可以扫描代码中的内存分配和释放情况,并发现潜在的内存泄漏问题。
常用的静态代码检查工具包括Coverity、PMD等。
2. 动态代码检查:通过运行时检查工具对程序进行监控,记录内存分配和释放的情况,检查是否有未释放的内存。
常用的动态代码检查工具包括Valgrind、Dr.Memory等。
3. 内存使用分析工具:通过监控程序的内存使用情况,包括内存的分配与释放,内存占用量等信息,来判断是否存在内存泄漏。
常用的内存使用分析工具有Google Performance Tools、Eclipse Memory Analyzer 等。
二、定位内存泄漏的方法有以下几种:1.添加日志:在程序中添加日志跟踪内存的分配与释放情况,当发现内存没有被释放时,通过日志定位问题的位置。
可以通过添加打印语句或者使用专门的日志工具来完成日志记录。
2. 使用内存调试工具:内存调试工具可以跟踪程序中的内存分配和释放情况,并将未被释放的内存标记出来。
通过分析工具提供的报告,可以定位内存泄漏的位置。
常用的内存调试工具有Valgrind、Dr.Memory等。
3. 内存堆栈分析:当程序出现内存泄漏时,通过分析内存堆栈可以得到导致内存泄漏的代码路径。
可以使用工具来进行内存堆栈分析,例如Eclipse Memory Analyzer。
三、解决内存泄漏的方法有以下几种:1. 显式释放内存:在程序中显式地调用释放内存的函数,确保内存被正确地释放。
例如,在使用动态内存分配函数malloc或new分配内存后,必须使用free或delete释放内存。
2. 自动垃圾回收:使用编程语言或框架提供的垃圾回收机制,自动释放不再使用的内存。
tcmalloc查找内存泄漏的原理

深入理解tcmalloc查找内存泄漏的原理一、简介tcmalloc是Google开发的一个高性能的内存分配器,被广泛应用于各种软件系统中。
它提供了一种有效的方法来管理和追踪内存使用情况,从而帮助我们发现和修复内存泄漏问题。
本文将详细介绍tcmalloc查找内存泄漏的原理。
二、tcmalloc概述tcmalloc是Thread-Caching Malloc的缩写,它是一个线程缓存的malloc实现。
tcmalloc的主要目标是提供高效的多线程内存管理,并具有以下特点:1. 高效的并发内存分配:tcmalloc使用线程局部缓存来避免锁竞争,从而提高了多线程环境下的内存分配性能。
2. 精确的内存分配跟踪:tcmalloc能够精确地跟踪每个内存分配的大小和位置,以便在程序运行结束时检查是否存在内存泄漏。
3. 灵活的配置选项:tcmalloc提供了一些配置选项,可以根据应用程序的需求进行调整,以满足不同的内存管理需求。
三、tcmalloc查找内存泄漏的原理tcmalloc通过以下步骤来查找内存泄漏:1. 内存分配跟踪:当程序进行内存分配时,tcmalloc会记录每个分配的大小和位置信息。
这些信息将被存储在一个内部的数据结构中,以便后续的内存泄漏检测。
2. 内存释放跟踪:当程序释放内存时,tcmalloc会更新相应的内存块的状态,将其标记为已释放。
这样,我们就可以知道哪些内存块已经被释放,而哪些仍然存在于内存中。
3. 内存泄漏检测:在程序运行结束时,tcmalloc会遍历所有的内存块,检查它们的状态。
如果发现有未释放的内存块,那么就可以认为存在内存泄漏。
tcmalloc 会输出相关的统计信息,包括泄漏的内存块数量和总大小。
4. 内存泄漏定位:tcmalloc还可以提供一些额外的信息,帮助我们定位内存泄漏的具体位置。
例如,它可以输出每个内存块的分配堆栈信息,从而帮助我们找到导致内存泄漏的代码路径。
四、总结tcmalloc作为一款高性能的内存分配器,不仅提供了高效的多线程内存管理,还具备查找内存泄漏的能力。
检测内存泄露的方法

检测内存泄露的方法
1. 手动检查代码:内存泄漏通常是由于程序未正确释放动态分配的内存造成的,因此,开发人员可以手动审查他们的代码,以确保内存管理的正确性。
2. 静态代码分析工具:静态代码分析工具(如PVS-Studio、Coverity等)可以检测代码中的潜在问题和内存泄漏。
他们分析代码以查找未释放的内存和其它资源。
3. 动态代码分析工具:动态代码分析工具(如Valgrind、Dr.Memory等)可以模拟应用程序的执行,并跟踪内存的分配和释放。
这些工具可以检测内存泄漏和其它内存管理问题。
4. 内存分析工具:内存分析工具(如Heap Profiler、Memory Analyzer等)可以帮助开发人员识别内存泄漏并找到其原因。
他们可以跟踪内存分配和释放,并生成详细的报告,以帮助开发人员定位问题。
5. 内存泄漏检测工具:内存泄漏检测工具(如LeakCanary等)专门用于检测Android平台上的内存泄漏。
他们可以在应用程序中检测出未释放的对象,并
提供详细的报告和堆栈跟踪,以帮助开发人员找到问题所在。
内存泄漏排查流程过程和方法

内存泄漏排查流程过程和方法一、内存泄漏的初步判断1.1 观察系统症状当怀疑有内存泄漏时,首先得看看系统的一些表现。
如果系统变得越来越慢,就像蜗牛爬一样,那很可能是内存泄漏捣的鬼。
还有啊,程序运行的时间越长,可用内存就越少,这也是个很明显的信号。
就好比一个水桶有个小漏洞,水一直流出去,桶里的水就越来越少啦。
1.2 查看资源占用情况我们可以查看系统的资源监视器之类的工具。
看看内存的使用量是不是一直往上涨,就像气球不断被吹气一样。
如果内存使用量只增不减,那内存泄漏的可能性就很大了。
二、定位内存泄漏的源头2.1 代码审查这时候就得卷起袖子好好审查代码啦。
看看有没有一些地方在不断地创建对象,但是却没有及时释放。
比如说,有些新手写代码,就像一个马虎的厨师做菜,只知道往锅里加料,却忘记把用过的锅刷干净。
像在循环里不断创建新的对象,却没有在合适的地方销毁,这就是典型的内存泄漏隐患。
2.2 借助工具检测有不少好用的工具能帮我们大忙呢。
像Valgrind这个工具就像是一个侦探,能够嗅出内存泄漏的蛛丝马迹。
它可以详细地告诉我们是哪段代码在搞鬼,就像给我们指出小偷藏在哪里一样。
还有一些编程语言自带的内存分析工具,也非常实用。
2.3 分析内存分配模式我们要仔细分析内存是怎么分配的。
如果发现有一些内存块被分配后,很长时间都没有被再次使用,就像被遗忘在角落里的宝藏一样,那这里就很可能存在内存泄漏。
而且如果大量的小内存块不断被分配,却没有被回收,这也可能是内存泄漏的一种表现形式。
三、解决内存泄漏问题3.1 修复代码逻辑一旦确定了内存泄漏的源头,就要赶紧修复代码逻辑。
如果是对象没有及时释放,那就得在合适的地方加上释放的代码。
这就好比收拾房间,用过的东西要放回原位或者扔掉,不能让它们一直在房间里占地方。
3.2 进行测试验证修复完代码可不能就这么算了,还得进行测试验证。
要确保内存泄漏的问题真的被解决了。
可以长时间运行程序,看看内存使用情况是不是稳定了。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
今咱们来聊聊JVM 堆外内存泄露的BUG是如何查找的前言JVM的堆外内存泄露的定位一直是个比较棘手的问题。
此次的Bug查找从堆内内存的泄露反推出堆外内存,同时对物理内存的使用做了定量的分析,从而实锤了Bug的源头。
笔者将此Bug分析的过程写成博客,以飨读者。
由于物理内存定量分析部分用到了linux kernel虚拟内存管理的知识,读者如果有兴趣了解请看ulk3(《深入理解linux内核第三版》)内存泄露Bug现场一个线上稳定运行了三年的系统,从物理机迁移到docker环境后,运行了一段时间,突然被监控系统发出了某些实例不可用的报警。
所幸有负载均衡,可以自动下掉节点,如下图所示:登录到对应机器上后,发现由于内存占用太大,触发OOM,然后被linux系统本身给kill了。
应急措施紧急在出问题的实例上再次启动应用,启动后,内存占用正常,一切Okay。
奇怪现象当前设置的最大堆内存是1792M,如下所示:-Xmx1792m -Xms1792m-Xmn900m -XX:PermSize=256m -XX:MaxPermSize=256m -server -Xss512k查看操作系统层面的监控,发现内存占用情况如下图所示:上图蓝色的线表示总的内存使用量,发现一直涨到了4G后,超出了系统限制。
很明显,有堆外内存泄露了。
推荐一个交流学习群:478030634里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。
还能领取免费的学习资源,目前受益良多:查找线索gc日志一般出现内存泄露,笔者立马想到的就是查看当时的gc日志。
本身应用所采用框架会定时打印出对应的gc日志,遂查看,发现gc日志一切正常。
对应日志如下:查看了当天的所有gc日志,发现内存始终会回落到170M左右,并无明显的增加。
要知道JVM进程本身占用的内存可是接近4G(加上其它进程,例如日志进程就已经到4G了),进一步确认是堆外内存导致。
排查代码打开线上服务对应对应代码,查了一圈,发现没有任何地方显式利用堆外内存,其没有依赖任何额外的native方法。
关于网络IO的代码也是托管给Tomcat,很明显,作为一个全世界广泛流行的Web服务器,Tomcat不大可能有堆外内存泄露。
进一步查找由于在代码层面没有发现堆外内存的痕迹,那就继续找些其它的信息,希望能发现蛛丝马迹。
Dump出JVM的Heap堆由于线上出问题的Server已经被kill,还好有其它几台,登上去发现它们也占用了很大的堆外内存,只是还没有到触发OOM 的临界点而已。
于是就赶紧用jmap dump了两台机器中应用JVM的堆情况,这两台留做现场保留不动,然后将其它机器迅速重启,以防同时被OOM导致服务不可用。
使用如下命令dump:jmap -dump:format=b,file=heap.bin [pid]使用MAT分析Heap文件挑了一个heap文件进行分析,堆的使用情况如下图所示:一共用了200多M,和之前gc文件打印出来的170M相差不大,远远没有到4G的程度。
不得不说MAT是个非常好用的工具,它可以提示你可能内存泄露的点:这个cachedBnsClient类有12452个实例,占用了整个堆的61.92%。
查看了另一个heap文件,发现也是同样的情况。
这个地方肯定有内存泄露,但是也占用了130多M,和4G相差甚远。
查看对应的代码系统中大部分对于CachedBnsClient的调用,都是通过注解Autowired的,这部分实例数很少。
唯一频繁产生此类实例的代码如下所示:@Overridepublic void fun() {BnsClient bnsClient = new CachedBnsClient(); // do something return ;}此CachedBnsClient仅仅在方法体内使用,并没有逃逸到外面,再看此类本身public class CachedBnsClient { private ConcurrentHashMap> authCache = new ConcurrentHashMap>(); private ConcurrentHashMap> validUriCache = new ConcurrentHashMap>(); private ConcurrentHashMap> uriCache = new ConcurrentHashMap>(); ......}没有任何static变量,同时也没有往任何全局变量注册自身。
换言之,在类的成员(Member)中,是不可能出现内存泄露的。
当时只粗略的过了一过成员变量,回过头来细想,还是漏了不少地方的。
更多信息由于代码排查下来,感觉这块不应该出现内存泄露(但是事实确是如此的打脸)。
这个类也没有显式用到堆外内存,而且只占了130M,和4G比起来微不足道,还是先去追查主要矛盾再说。
使用jstack dump线程信息现场信息越多,越能找出蛛丝马迹。
先用jstack把线程信息dump 下来看下。
这一看,立马发现了不同,除了正常的IO线程以及框架本身的一些守护线程外,竟然还多出来了12563多个线程。
'Thread-5' daemon prio=10 tid=0x00007fb79426e000 nid=0x7346 waiting on condition [0x00007fb7b5678000] ng.Thread.State: TIMED_WAITING (sleeping)at ng.Thread.sleep(Native Method)at com.xxxxx.CachedBnsClient$1.run(CachedBnsClient.java:62)而且这些正好是运行再CachedBnsClient的run方法上面!这些特定线程的数量正好是12452个,和cachedBnsClient数量一致!再次check对应代码原来刚才看CachedBnsClient代码的时候遗漏掉了一个关键的点!publicCachedBnsClient(BnsClient client) { super(); this.backendClient = client; new Thread() { @Overridepublic void run() { for (; ; ) {refreshCache(); try {Thread.sleep(60 * 1000);} catch (InterruptedException e) {logger.error('出错',e);}}}}这段代码是CachedBnsClient的构造函数,其在里面创建了一个无限循环的线程,每隔60s启动一次刷新一下里面的缓存!找到关键点在看到12452个等待在CachedBnsClient.run的业务的一瞬间笔者就意识到,肯定是这边的线程导致对外内存泄露了。
下面就是根据线程大小计算其泄露内存量是不是确实能够引起OOM了。
发现内存计算对不上由于我们这边设置的Xss是512K,即一个线程栈大小是512K,而由于线程共享其它MM单元(线程本地内存是是现在线程栈上的),所以实际线程堆外内存占用数量也是512K。
进行如下计算:12563 * 512K = 6331M = 6.3G整个环境一共4G,加上JVM堆内存1.8G(1792M),已经明显的超过了4G。
(6.3G 1.8G)=8.1G > 4G如果按照此计算,应用应用早就被OOM了。
怎么回事呢?为了解决这个问题,笔者又思考了好久。
如下所示:Java线程底层实现JVM的线程在linux上底层是调用NPTL(Native Posix Thread Library)来创建的,一个JVM线程就对应linux的lwp(轻量级进程,也是进程,只不过共享了mm_struct,用来实现线程),一个thread.start就相当于do_fork了一把。
其中,我们在JVM启动时候设置了-Xss=512K(即线程栈大小),这512K中然后有8K是必须使用的,这8K是由进程的内核栈和thread_info公用的,放在两块连续的物理页框上。
如下图所示:众所周知,一个进程(包括lwp)包括内核栈和用户栈,内核栈thread_info用了8K,那么用户态的栈可用内存就是:512K-8K=504K如下图所示:Linux实际物理内存映射事实上linux对物理内存的使用非常的抠门,一开始只是分配了虚拟内存的线性区,并没有分配实际的物理内存,只有推到最后使用的时候才分配具体的物理内存,即所谓的请求调页。
如下图所示:查看smaps进程内存使用信息使用如下命令,查看cat /proc/[pid]/smaps > smaps.txt实际物理内存使用信息,如下所示:7fa69a6d1000-7fa69a74f000 rwxp 00000000 00:00 0 Size: 504 kBRss: 92 kBPss: 92 kBShared_Clean: 0 kBShared_Dirty: 0 kBPrivate_Clean: 0 kBPrivate_Dirty: 92 kBReferenced: 92 kBAnonymous: 92 kBAnonHugePages: 0 kBSwap: 0 kBKernelPageSize: 4 kBMMUPageSize: 4 kB7fa69a7d3000-7fa69a851000 rwxp 00000000 00:00 0 Size: 504 kBRss: 152 kBPss: 152 kBShared_Clean: 0 kBShared_Dirty: 0 kBPrivate_Clean: 0 kBPrivate_Dirty: 152 kBReferenced: 152 kBAnonymous: 152 kBAnonHugePages: 0 kBSwap: 0 kBKernelPageSize: 4 kBMMUPageSize: 4 kB搜索下504KB,正好是12563个,对了12563个线程,其中Rss表示实际物理内存(含共享库)92KB,Pss表示实际物理内存(按比例共享库)92KB(由于没有共享库,所以Rss==Pss),以第一个7fa69a6d1000-7fa69a74f000线性区来看,其映射了92KB的空间,第二个映射了152KB 的空间。
如下图所示:挑出符合条件(即size是504K)的几十组看了下,基本都在92K-152K之间,再加上内核栈8K(92152)/2 8K=130K,由于是估算,取整为128K,即反映此应用平均线程栈大小。