内存管理

垃圾回收机制

JavaScript具有自动垃圾收集机制,执行环境会负责管理内存。垃圾回收的原理是:垃圾收集器周期性定时查找不再继续使用的变量,然后释放其占用的内存。

但这个过程中,会产生一些小问题

  • 性能影响 - GC 会消耗计算能力去决定什么对象应该释放
  • 无法预测的停顿 - 传统 JS GC 是 stop-the-world 的回收方式(即)。现代 GC 实现尝试去避免 stop-the-world 的回收方式

以函数中的局部变量为例,当函数执行完毕,局部变量不再使用,占用的内存则会被回收。标识无用变量的策略主要有两种:标记清除、引用计数。

标记清除

标记清除是最常用的垃圾回收方式。垃圾收集器会给内存中的所有变量都打上一个标记,当变量进入执行环境中时,标记为“进入环境”,离开环境时,则标记为“离开环境”可回收的状态。

引用计数

引用计数回收机制不太常见。原理是跟踪记录每个值被引用的次数。当引用类型值被赋给某个变量时,这个值的引用次数就增加1,反之则减一。当这个引用类型值变为0,该值所占内存可被回收。

引用计数最大的问题是循环引用,循环引用是指对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。例如:

function problem(){
    var objectA = new Object();
    var objectB = new Object();

    objectA.someOtherObject = objectB;
    objectB.someOtherObject = objectA;
}

当函数执行完毕,离开执行环境时,objectAobjectB的引用计数仍然为1,因此不会被释放,也就引起了内存泄漏。

V8引擎已经能很好的处理循环引用的情况,即循环引用的对象当没有外部引用时,会被正确的GC

解除引用

为了使用最少的内存可以让页面获得更好的性能。对于大部分全局变量可以采用将其值设置为Null的方式来释放引用,例如:

function createPerson(name){
    var localPerson = new Object();
    localPerson.name = name;
    return localPerson;
}
var globalPerson = createPerson("Nicholas");
// 手工解除 globalPerson 的引用
globalPerson = null;

闭包引起的内存泄漏

IE6下的内存泄漏

在IE一些低版本浏览器中(主要是IE6)由于垃圾回收机制的问题(BOM对象和DOM对象由COM对象的形式实现,而COM对象使用引用计数进行垃圾回收),存在循环引用的问题,因此会导致内存泄漏。

Chrome 的 V8 对象实现和IE不同,此种情况下并不会内存泄漏

举个例子说明:

window.onload = function(){
    var el = document.getElementById("id");
    el.onclick = function(){
        alert(el.id);
    }
}

执行给元素绑定事件时,元素绑定了匿名函数,但在匿名函数中同时又引用了元素的id,存在循环引用。

window.onload = function(){
    var el = document.getElementById("id");
    var id = el.id; //解除循环引用
    el.onclick = function(){
        alert(id); 
    }
    el = null; // 将闭包引用的外部函数中活动对象清除
}

这种情况基本可以看做是少数浏览器的bug,跟js本身没有关系。

V8下的内存泄漏

例子来源于这里

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, 100)

上述情况中 unused 的函数中持有了 originalThing 的引用, 使得每次旧的对象不会释放从而导致内存泄漏

我们可以在 Chrome 调试工具中性能(Performance)标签页,记录内存使用情况.可以看到在这个例子在执行时,内存使用在稳步上升,即发生了内存泄漏。

results matching ""

    No results matching ""