转载和积累系列 - js中的垃圾回收机制

js具有自动垃圾回收机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。而在C和C++之类的语言中,开发人员的一项基本任务就是手工跟踪内存的使用情况,这是造成许多问题的根源。在编写js程序时,开发人员不用关心内存使用问题,所需内存的分配以及无用内存的回收完全实现了自动管理。这种垃圾回收机制的原理其实很简单:找到那些不再继续使用的变量,然后释放其所占用的内存。为此,垃圾回收器会按照固定的时间间隔(或代码执行中预定的回收时间),周期性地执行这一操作。

  函数中的局部变量的生命周期:局部变量只在函数执行的过程中存在。而在这个过程中,会为局部变量在栈(或堆)内存上分配相应的空间,以便存储它们的值。然后在函数中使用这些变量,直至函数执行结束。此时,局部变量就没有存在的必要了,因此可以释放它们的内存以供将来使用。在这种情况下,很容易判断变量是否还有存在的必要;但并非所有情况下都这么容易就能得出结论。垃圾回收器必须跟踪哪个变量有用,哪个变量没用,对于不再有用的变量打上标记,以备将来收回其占用的内存。用于标识无用变量的策略可能会因实现而异,但具体到浏览器中的实现,则通常有两个策略。


  标记清除
  js中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。
  可以使用任何方式来标记变量。比如,可以通过翻转某个特殊的位来记录一个变量何时进入环境,或者使用一个“进入环境”变量列表及一个“离开环境”变量列表来跟踪哪个变量发生了变化。说到底,如何标记变量其实并不重要,关键在于采取什么策略。
  垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。

  到2008年为止,IE、Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除工的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。


  引用计数
  引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。

  Netscape Navigator3是最早使用引用计数策略的浏览器,但很快它就遇到一个严重的问题:循环引用。循环引用指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。


    function problem(){  
        var objA=new Object();  
        var objB=new Object();  
      
        objA.b=objB;  
        objB.a=objA;  
    }  

在这个例子中,objA和objB通过各自的属性相互引用;也就是说,这两个对象的引用次数都是2。在采用标记清除策略的实现中,由于函数执行之后,这两个对象都离开了作用域,因此这种相互引用不是问题。但采用引用计数策略的实现中,当函数执行完毕后,objA和objB还将继续存在,因为它们的引用次数永远不会是0。假如这个函数被重复多次调用,将导致大量内存得不到回收。为此Netscape在Navigator4中放弃了引用计数的方式,转而采用标记清除来实现其垃圾回收机制。可是,引用计数导致的麻烦并未终结。  我们知道,IE中有一部分对象并不是原生js对象。例如,其DOM和BOM中的对象就是使用C++以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数策略。因此,即使IE的js引擎采用标记清除策略来实现,但js访问的COM对象依然是基于引用计数策略的。换句话说,只要在IE中涉及COM对象,就会存在循环引用的问题。


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

 这个例子在一个DOM元素(element)与一个原生js对象(myObject)之间创建了循环引用。其中,变量myObject有一个名为element的属性指向element对象;而变量element也有一个属性名为o回指myObject。由于存在这个循环引用,即使例子中的DOM从页面中移除,它也永远不会被回收。  为了避免类似这样的循环引用问题,最好是在不使用它们的时候手工断开原生js对象与DOM元素之间的连接。例如,可以使用下面的代码消除前面例子创建的循环引用:

    myObject.element=null;  
    element.o=null;  

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

你可能感兴趣的:(转载和积累系列 - js中的垃圾回收机制)