20170605-内存泄漏和垃圾回收

垃圾回收的必要性

因为字符串、对象和数组没有固定大小,因此当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次建立字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们可以被再用,不然,JavaScript的解释器将会消耗完系统中全部可用的内存,形成系统崩溃。 ---《JavaScript权威指南(第四版)》javascript

JavaScript的解释器能够检测到什么时候程序再也不使用一个对象了,当他肯定了一个对象是无用的时候,他就知道再也不须要这个对象,能够把它所占用的内存释放掉了。例如:java

var a = "before";
var b = "override a";
var a = b; //重写a

这段代码运行以后,“before”这个字符串失去了引用(以前是被a引用),系统检测到这个事实以后,就会释放该字符串的存储空间以便这些空间能够被再利用。node

垃圾回收原理

最常采用的垃圾回收有两种方法:标记清除、引用计数git

标记清除

当执行流入到一个函数中时,会建立该函数的执行环境,执行环境中的变量都会被标记为“进入环境”,从逻辑上讲,永远不能释放“进入执行环境”变量所占用的内存。由于只要执行流入相应的执行环境,就可能会用到这些变量。
垃圾收集器在运行的时候会给存储在内存的中的变量都加上标记。而后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此以后再被加上标记的变量将被视为准备删除的变量。最后,垃圾收集器完成内存清除工做,销毁那些带标记的值并回收它们所占用的内存空间。github

引用计数

另外一种不太常见的垃圾回收策略是引用计数。引用计数的含义是跟踪记录每一个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,若是包含对这个值引用的变量又取得了另一个值,则这个值的引用次数就减1。当这个引用次数变成0时,则说明没有办法再访问这个值了,于是就能够将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。chrome

可是用这种方法存在问题:数组

function problem(){
    var objA = new Object()
    var objB = new Object()
    objA.someOtherObject = objB
    objB.someOtherObject = objA
}

在这个例子中,objA和objB经过各自的属性相互引用;也就是说这两个对象的引用次数都是2。在采用引用计数的策略中,因为函数执行以后,这两个对象都离开了做用域,函数执行完成以后,objA和objB还将会继续存在,由于他们的引用次数永远不会是0。这样的相互引用若是说很大量的存在就会致使大量的内存泄露。缓存

引发内存泄漏的操做

用全局变量缓存数据

将全局变量做为缓存数据的一种方式,将以后要用到的数据都挂载到全局变量上,用完以后也不手动释放内存(由于全局变量引用的对象,垃圾回收机制不会自动回收),全局变量逐渐就积累了一些不用的对象,致使内存泄漏闭包

var x = [];
function grow() {
  x.push(new Array(1000000).join('x'));
  /*
     使用x数组进行某些操做
  */
  setTimeout(grow, 1000);
}
grow()

没有清理的DOM元素引用

(function () {
  var nodes = '';
  var item = {
    // 为了凸显
    name: new Array(1000000).join('x')
  }
  nodes = document.getElementById("nodes")
  nodes.item = item
  nodes.parentElement.removeChild(nodes)
})()

notes变量指向的是页面中的一个元素(也是内存中的一块空间),当将 notes 对应的元素从页面中移除后,其在内存中对应的空间因为仍然由notes变量指向(引用),所以垃圾回收机制不会将这块内存空间回收,从而致使内存泄漏ide

 被遗忘的定时器或者回调

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);

这样的代码很常见, 若是id为Node的元素从DOM中移除, 该定时器仍会存在, 同时, 由于回调函数中包含对someResource的引用, 定时器外面的someResource也不会被释放.

闭包循环引用

var theThing = null  
var replaceThing = function () {
  var originalThing = theThing
  var unused = function () {
    if (originalThing)
      console.log("hi")
  }
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log(someMessage)
    }
  };
};
setInterval(replaceThing, 1000)

这种内存泄漏的分析请参考here

参考

《JavaScript权威指南》
javascript典型内存泄漏及chrome的排查方法

相关文章
相关标签/搜索