内存泄漏会影响系统性能和程序稳定性,甚至由于泄漏问题堆积,最终导致内存溢出,服务宕机。作为测试人员,在性能测试时内存泄漏问题,应该如何分析定位呢?本文整理了我常用的分析定位方法,并通过一个真实案例,分享如何从性能指标异常出发,逐步定位并修复内存泄漏问题。
内存泄漏的性能指标表现
内存使用率递增:未及时释放掉创建的对象或资源。
CPU使用率递增:GC线程占用大量CPU,CPU因频繁创建/销毁对象而升高。
吞吐量递减:系统处理能力随内存压力增大而降低。
响应时间增长:因GC停顿或内存不足时频繁换页,接口平均响应时间逐渐上升。
内存泄漏的危害
频繁GC:系统分配给每个应用的内存资源都是有限的,内存泄漏导致其他组件可用的内存变少后,一方面会使得GC的频率加剧,在发生GC的时候,所有进程都必须等待,GC的频率越高,用户越容易感应到卡顿。另一方面内存变少,可能使得系统额外分配给该对象一些内存,而影响整个系统的运行情况。
程序运行崩溃:一旦内存不足以给某些对象分配所需要的空间,将会导致程序崩溃,服务宕机。
内存泄漏的问题定位方法
控制变量法与排除法
原理:通过逐步注释代码中的事务逻辑,然后重新复现问题,观察瓶颈是否依旧存在。若瓶颈消失,说明被注释的事务存在导致内存泄漏的因素。
操作步骤:
梳理代码结构:对程序的整体架构和各个事务逻辑有清晰的了解,明确每个事务的功能和相互之间的调用关系。
分组注释:将代码中的事务逻辑按照一定规则进行分组,每次注释一组事务逻辑,然后重新编译运行程序,进行问题复现测试。
结果判断:如果在注释某组事务逻辑后,内存泄漏问题不再出现,那么可以进一步细化该组事务逻辑,继续使用控制变量法进行排查,直到定位到具体的有问题的事务。
日志对比分析
原理:开发人员输出每个事务的详细日志,通过对比程序刚开始运行部分的日志和运行一段时间后的日志,分析事务在时间、异常等方面的差异,找出可能导致内存泄漏的事务。
操作步骤:
日志采集:在程序运行过程中,确保开发人员输出每个事务的详细日志,包括事务开始时间、结束时间、执行结果、异常信息等。分别采集程序刚开始运行一段时间(如前 10 分钟)和运行较长时间(如 1 小时后)的日志。
日志筛选:从采集的日志中挑选几个具有代表性的接口请求的日志进行详细比较。代表性接口可以选择调用频繁、业务逻辑复杂的接口。
差异分析:重点关注事务的处理耗时和异常信息。如果某个事务的处理耗时随着时间不断递增,或者频繁出现异常抛出问题,那么这个事务很可能是导致内存泄漏的原因。
示例:在某订单服务系统中,日志显示用户登录事务在程序刚开始运行时处理时间平均为 1 秒,运行 1 小时后处理时间增加到 5 秒,且偶尔出现异常信息,那么登录事务就可能存在内存泄漏问题。
内存分析工具
原理:借助内存分析工具,如 VisualVM、MAT 等,实时监控程序的内存使用情况,生成内存快照,分析对象的创建和销毁过程,帮助定位内存泄漏的位置。
操作步骤:在程序运行时启动内存分析工具,让程序运行一段时间,期间触发内存泄漏问题。然后使用工具生成内存快照,分析快照中的对象信息,找出哪些对象占用了大量内存且没有被正确释放。
案例分析
问题背景:
某订单服务 v1.0 进行性能测试时,发现如下现象:
吞吐量一直下降、平均响应时间一直在上升;
VisualVM 内存曲线一直上涨;
通过 jstat 命令查看老年代一直为100%,而FGC的次数一直在上涨。
问题定位:
问题定位思路:通过 VisualVM 工具生成堆存储文件,并在本地通过该工具查看内存占用排名靠前的对象,再根据项目所在目录,拉取源码进行具体分析。
问题定位过程:
在 VisualVM 中生成下载 heapdump.hprof 文件,在本地 VisualVM 选择文件→装入→堆→文件,打开对应的堆转储文件后,看到每个类的对象内存占用情况,按大小降序排序,查看到内存占用较高的类有6个,逐一排查。
接着根据该根据类的打印路径找到项目对应目录,并进行了源码的分析,最终发现登录代码逻辑有问题:每次有用户登录,就会去写入session,没有判断用户是否已经登录,导致存储登录用户的对象内存占用快速膨胀,造成内存泄漏。
问题解决:
开发进行代码逻辑修改,增加用户登录判断。
效果验证:
修复后重新压测12小时,内存占用情况稳定,平均响应时间等其他指标符合预期无异常,FGC次数明显下降。
评论区