搞定JavaScript内存泄漏

先了解一下背景

本小节主要简述内存泄漏的背景,以及 Chrome 的Heap Profiler所用到的一些术语

众所周知JavaScript具有自动垃圾收集机制,不过随着前端页面越来越复杂,内存泄露问题还是时有发生。

JavaScript变量的内存占用

5种基本类型
  • 数字(Numbers) (如 3.14159..)
  • 布尔值(Booleans) (true或false)
  • 字符型(Strings) (如 'Werner Heisenberg')
  • Null
  • Undefined
动态的属性
typeof '111';//string  
typeof new String('111')//object  
var s = '123';  
var s2 = new String('123');  
s.color = 'red';  
s2.color = 'red';  
console.log(s.color);//undefined  
console.log(s2.color);//red
复制变量值
var num1 = 1;  
var num2 = num1;  
num1 = 3;  
console.log(num2);//1  
var obj1 = new Object();  
var obj2 = obj1;  
obj1.name = 'mdemo';  
console.log(obj2.name);//mdemo  
传递参数
function setName(obj){  
    obj.name = 'mdemo';
}
var person = new Object();  
setName(person);  
console.log(person.name);//mdemo  
function setName(obj){  
    obj.name = 'mdemo';
    obj = new Object();
    obj.name = 'demohi';
}
var person = new Object();  
setName(person);  
console.log(person.name);//mdemo  
对象删除
var obj1 = new Object();  
var obj2 = obj1;  
obj1.name = 'mdemo';  
obj1 = null;  
console.log(obj2.name);//mdemo  

内存中的对象

如果我们把内存想象成一个包含基本类型(像数字和字符串)和对象(关联数组)的图表。它可能看起来像下面这幅一系列相关联的点组成的图。

一个对象有两种使用内存的方法:

  • 对象自身直接使用
  • 隐含的保持对其它对象的引用,这种方式会阻止垃圾回收(简称GC)对那些对象的自动回收处理。

这个可以在Heap Profiler中有所体现,如下图我们看到内存占用的两项,直接占用内存(Shallow Size)和占用总内存(Retained Size)

  • 直接占用内存(Shallow Size,不包括引用的对象占用的内存)
  • 占用总内存(Retained Size,包括引用的对象所占用的内存)

GC 回收

  • 内存图从 root 开始,root 可以理解是浏览器中的 window 对象,Node.js 中的 Global 对象
  • 9与10无法与 root 相连,将会被 GC 回收掉
  • 节点离 root 的最近距离被称为distance,如下图可以在Heap Profiler中查看到
  • 上图2的 distance 为1,8的 distance 为4

准备一下调试环境

最新版 Chrome + 隐私模式

  • 最新版的 Chrome 可以拥有最佳的 devtools
  • 隐私模式是为了避免插件等的干扰(调试的时候,关闭所有插件在隐私模式下开启)

正式开始,学习一下工具

使用 timeline发现内存泄露

Example 1: Watch the memory grow

使用 timeline 查看 GC Event 耗时

Example 2: Watching the GC work

使用Heap Snapshot查看详细内存情况

Summary 视图

Example 3: Scattered objects

Compare 视图

Example 11:Verifying Action Cleanness

使用 Heap Allocations

Example 6: Leaking DOM nodes

  • 每条竖线代表一次新的内存生成
  • 灰色的竖线说明内存被回收
  • 蓝色的竖线说明生成后,没有被回收,有可能是内存泄露
  • 可以选定特定的蓝线,查看这次生成的内容,方便查找内存泄露问题

实战

Eval is evil (almost always)

Example 7: Eval is evil (almost always)

      function createEvalClosure() {
        var smallStr = 'x';
        var largeStr = new Array(1000000).join('x');
        return function eC() {
          eval('');
          return smallStr;
        };
      }

  • eval会导致不被引用的变量,也无法释放

dom引起的内存泄露

  • 黄色是指直接被 js变量所引用,在内存里
  • 红色是指间接被 js变量所引用,如上图,refB 被 refA 间接引用,导致即使 refB 变量被清空,也是不会被回收的
  • 子元素 refB 由于 parentNode 的间接引用,只要它不被删除,它所有的父元素(图中红色部分)都不会被删除

Example 9: DOM leaks bigger than expected

相关链接

文中所有 example 源码

example 索引

参考的页面&致谢

Chrome 开发者工具中文手册,感谢良心翻译

chrome 官方文档需翻墙

App之性能优化

Understand memory leaks in JavaScript applications

avaScript Profiling With The Chrome Developer Tools

Memory leaks

: ) boke.io