内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放。短期一般没有明显异常现象,但持续泄漏一定时间后,应用层面会出现交易TPS下降、响应耗时增长、交易成功率下降、CPU升高等异常现象,甚至出现内存溢出,系统宕机等现象,其危害相当严重。
本文对WAS内存泄漏测试过程中的经验进行小结,通过对GC输出分析内存泄漏的最常用操作和场景分析思路的汇总,为测试人员高效高质量的完成性能测试工作提供技术指导。
一、WebSphere 应用服务器的 GC 日志获取
(1)首次获取日志文件,需登陆 WebSphere 公共管理控制台,开启“详细垃圾回收”,设置初始堆大小和最大堆大小。后期测试前可事先确认,一般情况下是不需要更改配置的。
(2)测试执行前可有选择获取dump 文件和 Javacore 文件,以备后期分析。获取 dump 文件步骤如下:登陆 WebSphere 公共管理控制台,选中 server,点击“堆 转 储”和“java核心”按钮 。
(3)执行性能疲劳测试。因内存泄漏不易暴露,因此建议:一是内存泄漏测试的执行时间不能太短,建议放在疲劳测试中进行;二是铺底数据量建议与预期的最大生产数据量保持一致;三是测试执行数据量尽可能模拟生产最大数据运行场景,例如报表下载功能,考虑查询全量数据的下载场景。
(4)获取日志。在执行测试结束后,测试人员获取SystemOut.log应用日志和native_stderr.log 详细内存回收日志文件。
二、查看GC 曲线图
目前工作中使用的 GC 日志分析工具是PMAT(IBM Pattern Modeling and Analysis Tool for Java Garbage Collector),使用PMAT打开 native_stderr.log 文件,点击 Analysis 菜单,选择 Graph View All,或直接右键选择 Graph View All,根据需要选择查看的曲线。
曲线可选项含义如下图所示:
其中:具体指标数据可通过 GC View Part 表格数据的形式获得。
三、应用程序内存泄漏分析
性能测试人员可以通过以下几个文件和相关指标对内存泄漏展开辅助分析:
1、GC 日志分析
(1)GC 频率
GC 频率可通过选择 Interval(since)曲线图直观查看,也可以通过日志指标数据计算获得(GC 频率= Number of Garbage Collections / (Last Garbage Collection - First Garbage Collection))。
如果Full GC 频率较高,达到数秒一次,则需要检查系统现在运行情况是否合理,是否存在内存泄漏情况。
项目组曾经在性能测试时遇到过随着疲劳测试的进行,GC频率越来越快的现象,如下图所示:
通过dump文件分析,发现日志类变量定义未使用final修饰导致产生大对象,通过优化,将日志类log变量定义为static final后对比测试,GC频率恢复均匀正常。
(2)检查 GC 过程中长存区的变化
常存区的已用内存如持续不断增大,每次垃圾回收后可用的内存依然持续降低表明系统可能存在内存泄漏。
(3)检查 GC 空间配置和使用情况
由于 Java 程序所能使用的堆空间上限完全取决于 JVM 启动时的参数配置,当堆空间上限参数设置过低,即使操作系统物理内存空闲较多,应用程序也无法使用。因此在性能结果分析时,可关注 GC 类型、GC 申请空间大小和新生代空间大小等配置信息。
同时需要关注GC空间使用情况以判断JVM 堆的使用是否合理,新生代的空间大小配置和长存区的空间大小配置是否满足当前业务场景要求。是否会存在虽然进行了GC,但堆空间的使用仍不能满足申请空间的要求,系统出现频繁GC或内存溢出现象。
(4)检查是否存在大对象
依据大对象使用区域和堆内存的使用情况,结合heapdump判断应用程序中是否存在大对象。测试案例设计时需要考虑应用程序对大数据量事物处理稳定性的测试,例如接口报文考虑考虑大报文的传输测试,下载考虑大数据量报表的全表下载。
(5)关注 GC 过程中是否清理了过多引用对象
当发现 GC 过程中清理了过多引用对象,需要检查被测系统的代码编写是否符合规范。
(6)关注 GC 整个过程消耗的时间和垃圾回收中断时间
过长的 GC 会直接影响到系统的性能,测试人员在分析时可关注以下几个指标或曲线:overhead、Total Garbage Collection pause、Duration。
2、Heapdump文件分析
打开 dump 文件,如果服务器存在内存泄漏的可能,则会出现下图红框所示的“Reference Tree”界面。Reference Tree 中列出了当前堆中占比最大的对象信息。在其中选择占比最大一项,一层层点开,最后找到类信息,结合程序代码可定位问题所在。
也可以通过菜单 Analysis>Tree View 进行内存泄漏分析,堆内存占用比例越大说明内存消耗越多,然后针对对象所处的程序块进行连带分析,确定产生内存泄漏的原因。
3、Javacore 文件分析
关注线程的运行情况,可以查看不同状态的线程执行堆栈,提交项目组分析堆栈找到其中属于系统应用的代码。如下图所示:
在分析过程中需要重点关注的线程有:
·死锁,Deadlock:死锁线程,一般指多个线程调用间,进入相互资源占用,导致一直等待无法释放的情况。
·执行中,Runnable:一般指该线程正在执行状态中,该线程占用了资源,正在处理某个请求,有可能正在传递 SQL 到数据库执行,有可能在对某个文件操作,有可能进行数据类型等转换。
·等待资源,Waiting on condition:如果堆栈信息明确是应用代码,则证明该线程正在等待资源,一般是大量读取某资源,且该资源采用了资源锁的情况下,线程进入等待状态,等待资源的读取。又或者,正在等待其他线程的执行等。
·阻塞,Blocked:是指当前线程执行过程中,所需要的资源长时间等待却一直未能获取到,被容器的线程管理器标识为阻塞状态,可以理解为等待资源超时的线程。这种情况在 was 的日志中,一般可以看到 CPU 饥渴,或者某线程已执行了 XX 秒的信息。
经过一段时间的项目联机性能测试实践,测试人员对内存泄漏查找定位、GC 曲线图加深了理解,对 WAS 内存泄漏分析有了一定的测试经验。在后续的工作中,我们将继续不断回顾总结,持续提升测试人员测试技能,增强信息安全意识,多筹并举保障持续提升系统稳定性。
作者:李琳