JavaScript垃圾收集-标记清除和引用计数

  JavaScript具备自动垃圾收集机制,执行环境会负责管理代码执行过程当中使用的内存。算法

 

  垃圾收集机制原理:垃圾收集器会按照固定的时间间隔(或代码执行中预约的收集时间), 周期性地执行这一操做:找出那些再也不继续使用的变量,而后释放其占用的内存。函数

 

1.标记清除spa

  JavaScript中最重用的垃圾收集方式是标记清除(mark-and-sweep)。Take is cheap, let me show you the code.设计

 

  当运行addTen()这个函数的时候,就是当变量进入环境时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,由于只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。指针

 

1 function addTen(num){  
2     var sum += num;  //垃圾收集已将这个变量标记为“进入环境”。
3     return sum;      //垃圾收集已将这个变量标记为“离开环境”。
4 }
5 addTen(10);  //输出20

 

  可使用任何方式来标记变量。好比,能够经过翻转某个特殊的位来记录一个变量什么时候进入环境, 或者使用一个“进入环境的”变量列表及一个“离开环境的”变量列表来跟踪哪一个变量发生了变化。说到底,如何标记变量其实并不重要,关键在于采起什么策略。code

 

如下举一个简单释放内存例子:对象

var user = {name : 'scott', age : '21', gender : 'male'}; //在全局中定义变量,标记变量为“进入环境”

user = null;  //最后定义为null,释放内存

 

  垃圾收集器在运行的时候会给存储在内存中的全部变量都加上标记(固然,可使用任何标记方式)。而后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此以后再被加上标记的变量将被视为准备删除的变量,缘由是环境中的变量已经没法访问到这些变量了。最后,垃圾收集器完成内存清除工做,销毁那些带标记的值并回收它们所占用的内存空间。blog

 

1.引用计数ip

  另外一种不太常见的垃圾收集策略叫作引用计数(reference counting)。引用计数的含义是跟踪记录每一个值被引用的次数。内存

  当声明了一个变量并将一个引用类型值赋值该变量时,则这个值的引用次数就是1.若是同一个值又被赋给另一个变量,则该值得引用次数加1。相反,若是包含对这个值引用的变量又取 得了另一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0时,则说明没有办法再访问这个值了,于是就能够将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那 些引用次数为零的值所占用的内存。 

  问题:循环引用。循环引用指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。请看下面这个例子

function problem(){     
    var objectA = new Object();
    var objectB = new Object(); 
 
    objectA.someOtherObject = objectB;
    objectB.anotherObject = objectA; 
} 

 

  在这个例子中,objectA 和 objectB 经过各自的属性相互引用;也就是说,这两个对象的引用次数都是 2。

  在采用标记清除策略的实现中,因为函数执行以后,这两个对象都离开了做用域,所以这种相互引用不是个问题。但在采用引用计数策略的实现中,当函数执行完毕后,objectA 和 objectB 还将继续存在,由于它们的引用次数永远不会是 0。

  假如这个函数被重复屡次调用,就会致使大量内存得不到回收。为此放弃了引用计数方式,转而采用标记清除来实现其垃圾收集机制。但是,引用计数致使的麻烦并未就此终结。

  IE 中有一部分对象并非原生 JavaScript 对象。例如,其 BOM 和 DOM 中的对象就是使用 C++以 COM(Component Object Model,组件对象模型)对象的形式实现的,而 COM对象的垃圾 收集机制采用的就是引用计数策略。

  所以,即便 IE的 JavaScript引擎是使用标记清除策略来实现的,但 JavaScript访问的 COM对象依然是基于引用计数策略的。换句话说,只要在IE中涉及 COM对象,就会存在循环引用的问题。

  下面这个简单的例子,展现了使用 COM对象致使的循环引用问题: 

 

var element = document.getElementById("some_element"); 
var myObject = new Object();
myObject.element = element; 
element.someObject = myObject; 

  这个例子在一个 DOM元素(element)与一个原生 JavaScript对象(myObject)之间建立了循环引用。

  其中,变量 myObject 有一个名为 element 的属性指向 element 对象;而变量 element 也有 一个属性名叫 someObject 回指 myObject。

  因为存在这个循环引用,即便将例子中的 DOM从页面中移除,它也永远不会被回收。

  为了不相似这样的循环引用问题,最好是在不使用它们的时候手工断开原生 JavaScript 对象与 DOM元素之间的链接。例如,可使用下面的代码消除前面例子建立的循环引用: 

myObject.element = null; 
element.someObject = null;

  将变量设置为 null 意味着切断变量与它此前引用的值之间的链接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存。

  为了解决上述问题,IE9把 BOM和 DOM对象都转换成了真正的 JavaScript对象。这样,就避免了两种垃圾收集算法并存致使的问题,也消除了常见的内存泄漏现象。

  参考《JavaScript高级程序设计》

相关文章
相关标签/搜索