排查 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.视图层有些控件重复添加,没有移除。


↙↙↙阅读原文可查看相关链接,并与作者交流