JavaScript垃圾搜集-标记清理和引用计数

优采云 发布时间: 2020-08-11 13:02

  JavaScript具有手动垃圾搜集机制,执行环境会负责管理代码执行过程中使用的显存。

  垃圾搜集机制原理:垃圾搜集器会依照固定的时间间隔(或代码执行中预定的搜集时间), 周期性地执行这一操作:找出这些不再继续使用的变量,然后释放其占用的显存。

  1.标记消除

  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

  可以使用任何形式来标记变量。比如,可以通过翻转某个特殊的位来记录一个变量何时步入环境, 或者使用一个“进入环境的”变量列表及一个“离开环境的”变量列表来跟踪那个变量发生了变化。说到底,如何标记变量虽然并不重要,关键在于采取哪些策略。

  以下举一个简单释放显存事例:

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

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

  垃圾搜集器在运行的时侯会给储存在显存中的所有变量都加上标记(当然,可以使用任何标记方法)。然后,它会去除环境中的变量以及被环境中的变量引用的变量的标记。而在此以后再被加上标记的变量将被视为打算删掉的变量,原因是环境中的变量早已难以访问到这种变量了。最后,垃圾搜集器完成显存清理工作,销毁这些带标记的值并回收它们所占用的显存空间。

  1.引用计数

  另一种不太常见的垃圾搜集策略称作引用计数(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高级程序设计》

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线