最近性能测试中遇到了一个内存泄漏相关的案例,在这里与大家分享。
什么是内存泄漏?
内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果[1]。
由于内存泄漏导致的缺陷具有隐蔽性、累积性的特点,技术人员通常不会直接观察到相关错误症状,而是通过系统性能表现逐渐降低或系统崩溃发现此类缺陷。
怎样发现内存泄漏缺陷?
疲劳测试是一般采用混合交易场景(待测交易按照一定比例),以一定的压力(通常不低于目标TPS)执行压力测试,验证混合场景长时间执行情况下系统的性能表现。
疲劳测试能够验证混合场景下系统有无长时间无故障稳定运行的能力,是否有内存泄漏等性能问题。
一般推荐测试执行时长不低于4小时(交易量大于日交易量)。
系统若存在内存泄漏问题,通常会在疲劳测试的过程中发现此类缺陷。
测试过程
测试背景
应用程序部署在PaaS上,PaaS资源为1个2C4G的pod;使用Oracle数据库,铺底数据500万,造数逻辑为一个客户ID对应5个不同的关联人ID,每个关联人ID下有10个产品。
每次发起参数为客户ID的查询请求时,将返回50条产品信息。
监控过程
疲劳测试时,4个并发用户的情况下,TPS先保持正常,运行40分钟左右TPS逐渐下降、响应时间上升,直到服务报错、出现失败的请求。
持续一段时间后,响应时间恢复正常、TPS恢复正常,这一现象周期反复出现。
监控发现,TPS下降期间的CPU使用率一直在2C附近,接近100%使用;凌晨2:40左右疲劳测试结束后,CPU使用率恢复正常。
通过使用性能监控工具Dynatrace发现,ParOldGen用满后不回收,交易挂起量增多,响应时间长。
生成HeapDump时,会被动执行GC,即内存回收,此时对应时间点的TPS上升,恢复正常。不使用上述方法,平均1个小时会主动回收一次,然后循环往复。
再并未主动内存回收的一个小时内,响应时间逐渐增大,TPS降低。
同时监控数据库服务器,发现数据库服务器的CPU和内存使用率并不高。初步判断程序存在内存泄漏问题,风险较大。
分析并解决问题
通过阅读程序代码,发现SQL语句并不复杂,只涉及到简单的where条件查询和group by分组。
但程序中出现了与下面代码类似的逻辑:
由于for循环体中出现了大量创建ArrayList对象的操作,导致大量内存被消耗得不到释放。修改代码逻辑如下:
通过将创建对象的过程放在for循环外,避免了内存的多次分配与过多占用,从而提升了应用程序的性能表现,解决了内存泄漏的问题。
[1]王皓. 一种内存泄漏检测技术的研究和实现[D].北京交通大学,2008.
作者:张东宇