使用Chrome Profiles排查Web App的JS内存泄露小结
一、概述
所测试的项目是带角色交互的APP,其教师端是Hybird APP(主要控件是Native,页面内容基于Webview),学生端是一个基于Webview的APP。在测试过程中,几十次使用教师端翻页按钮,出现教师端概率性Crash。或者教师端将带音频和视频的内容分发到学生端,点击学生端的触发音视频按钮,其学生端高概率的Crash。经过多次教师端连续发送到学生端,发现学生端会出现页面渲染慢,有时无法将内容内容显示清楚,见图一。
图一
教师端使用翻页按钮发送多道题目,教师端操作后卡顿或者崩溃,见图二。
二、现象分析
出现页面内容页面加载缓慢,可初步判断是渲染慢,可能是Pad性能不足或者程序内存泄漏。出现教师端发题Crash或者操作教师端加载缓慢,也可能是内存泄漏。所以我第一时间把出现问题Pad连接到PC,查看CPU和内存使用情况,见图三:
图三
其次,出现学生端崩溃的时候将Pad连接到DDMS,查看报错log,由log可以看出在使用音视频的时候出错,最终导致浏览器崩溃,见图四:
图四
最后,由渲染慢导致页面加载慢,以及崩溃的日子可初步排查是内存泄漏,可尝试使用Chrome Profiles分析。
三、使用Chrome Profiles分析
Google Chrome浏览器提供了非常强大的JS调试工具,Heap Profiling便是其中一个。Heap Profiling可以记录当前的堆内存(heap)快照,并生成对象的描述文件,该描述文件给出了当时JS运行所用到的所有对象,以及这些对象所占用的内存大小、引用的层级关系等等。这些描述文件为内存泄漏的排查提供了非常有用的信息。什么是heap?JS运行的时候,会有栈内存(stack)和堆内存(heap),当我们用new实例化一个类的时候,这个new出来的对象就保存在heap里面,而这个对象的引用则存储在stack里。程序通过stack里的引用找到这个对象。例如var a = [1,2,3];,a是存储在stack里的引用,heap里存储着内容为[1,2,3]的Array对象。
JS程序的内存溢出后,会使某一段函数体永远失效(取决于当时的JS代码运行到哪一个函数),通常表现为程序突然卡死或程序出现异常。这时我们就要对该JS程序进行内存泄漏的排查,找出哪些对象所占用的内存没有释放。这些对象通常都是开发者以为释放掉了,但事实上仍被某个闭包引用着,或者放在某个数组里面。
有时我们需要在程序中加入观察者模式(Observer)来解藕一些模块,但如果使用不当,也会带来内存泄漏的问题。排查这类型的内存泄漏问题,主要重点关注被引用的对象类型是闭包(closure)和数组Array的对象。下面以实际项目为例:
首先,选中“Take Heap Snapshot”,点击“Take Heap Snapshot”按钮,就可以拍下当前JS的heap快照,如图五、图六所示:
图五
图六
其次,点击Pad端不同业务操作按钮,结合柱状图可查看不同操作的内存释放情况,见图七:
图七
最后,基于前面的内存释放情况,可使用JS的heap快照查看对象应用关系。点击其中一个对象,能看到对象的引用层级关系,红色部分表示内存内存泄漏。查看到实际应用多,且带有红色,表示内存泄漏,见图八:
图八
四、结语
JS的灵活性既是优点也是缺点,平时写代码时要注意内存泄漏的问题。当代码量非常庞大的时候,就不能仅靠复查代码来排查问题,必须要有一些监控对比工具来协助排查。
之前排查内存泄漏问题的时候,总结出以下几种常见的情况:
1.闭包上下文绑定后没有释放;
2.观察者模式在添加通知后,没有及时清理掉;
3.定时器的处理函数没有及时释放,没有调用clearInterval方法;
4.视图层有些控件重复添加,没有移除。