细说javascript的垃圾收集

以下内容引用javascript高级程序设计第三版 中第4章变量,作用域和内存问题

JavaScript 具有自动垃圾收集机制


      JavaScript 具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。
  而在 CC++之类的语言中,开发人员的一项基本任务就是手工跟踪内存的使用情况,这是造成许多问
  题的一个根源。在编写 JavaScript 程序时,开发人员不用再关心内存使用问题,所需内存的分配以及无
  用内存的回收完全实现了自动管理。这种垃圾收集机制的原理其实很简单:找出那些不再继续使用的变
  量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间),
  周期性地执行这一操作。
  
  下面我们来分析一下函数中局部变量的正常生命周期。局部变量只在函数执行的过程中存在。而在
  这个过程中,会为局部变量在栈(或堆)内存上分配相应的空间,以便存储它们的值。然后在函数中使
  用这些变量,直至函数执行结束。此时,局部变量就没有存在的必要了,因此可以释放它们的内存以供
  将来使用。在这种情况下,很容易判断变量是否还有存在的必要;但并非所有情况下都这么容易就能得
  出结论。垃圾收集器必须跟踪哪个变量有用哪个变量没用,对于不再有用的变量打上标记,以备将来收
  回其占用的内存。用于标识无用变量的策略可能会因实现而异,但具体到浏览器中的实现,则通常有两
  个策略。
  

1、标记清除


  JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。当变量进入环境(例如,在函
  数中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变
  量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其
  标记为“离开环境”。

  可以使用任何方式来标记变量。比如,可以通过翻转某个特殊的位来记录一个变量何时进入环境,
  或者使用一个“进入环境的”变量列表及一个“离开环境的”变量列表来跟踪哪个变量发生了变化。说
  到底,如何标记变量其实并不重要,关键在于采取什么策略。
  
  垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方
  式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此之后再被加上标记
  的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器
  完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。
  
  到 2008 年为止,IE、Firefox、Opera、Chrome 和 Safari 的 JavaScript 实现使用的都是标记清除式的
  垃圾收集策略(或类似的策略),只不过垃圾收集的时间间隔互有不同。
  

2、引用计数

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

  Netscape Navigator 3.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。假如这个函数被重复多次调用,就会导致大量内存得
  不到回收。为此,Netscape 在 Navigator 4.0 中放弃了引用计数方式,转而采用标记清除来实现其垃圾收
  集机制。可是,引用计数导致的麻烦并未就此终结。

  我们知道,IE 中有一部分对象并不是原生 JavaScript 对象。例如,其 BOMDOM 中的对象就是
  使用 C++COM(Component Object Model,组件对象模型)对象的形式实现的,而 COM 对象的垃圾
  收集机制采用的就是引用计数策略。因此,即使 IE 的 JavaScript 引擎是使用标记清除策略来实现的,但
  JavaScript 访问的 COM 对象依然是基于引用计数策略的。换句话说,只要在 IE 中涉及 COM 对象,就会
  存在循环引用的问题。下面这个简单的例子,展示了使用 COM 对象导致的循环引用问题:
    

    var element = document.querySelector("#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 意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就
  会删除这些值并回收它们占用的内存。
  为了解决上述问题,IE9BOMDOM 对象都转换成了真正的 JavaScript 对象。这样,就避免了
  两种垃圾收集算法并存导致的问题,也消除了常见的内存泄漏现象。
   

性能问题


  垃圾收集器是周期性运行的,而且如果为变量分配的内存数量很可观,那么回收工作量也是相当大
  的。在这种情况下,确定垃圾收集的时间间隔是一个非常重要的问题。说到垃圾收集器多长时间运行一
  次,不禁让人联想到 IE 因此而声名狼藉的性能问题。IE 的垃圾收集器是根据内存分配量运行的,具体
  一点说就是 256 个变量、4096 个对象(或数组)字面量和数组元素(slot)或者 64KB 的字符串。达到
  上述任何一个临界值,垃圾收集器就会运行。这种实现方式的问题在于,如果一个脚本中包含那么多变
  量,那么该脚本很可能会在其生命周期中一直保有那么多的变量。而这样一来,垃圾收集器就不得不频
  繁地运行。结果,由此引发的严重性能问题促使 IE7 重写了其垃圾收集例程。

  随着 IE7 的发布,其 JavaScript 引擎的垃圾收集例程改变了工作方式:触发垃圾收集的变量分配、
  字面量和(或)数组元素的临界值被调整为动态修正。IE7 中的各项临界值在初始时与 IE6 相等。如果
  垃圾收集例程回收的内存分配量低于 15%,则变量、字面量和(或)数组元素的临界值就会加倍。如果
  例程回收了 85%的内存分配量,则将各种临界值重置回默认值。这一看似简单的调整,极大地提升了 IE
  在运行包含大量 JavaScript 的页面时的性能。

  事实上,在有的浏览器中可以触发垃圾收集过程,但不建议这样做。在IE 中,
  调用 window.CollectGarbage() 方法会立即执行垃圾收集。在 Opera 7 及更
  高版本中,调用 window.opera.collect() 也会启动垃圾收集例程。
  

管理内存


  使用具备垃圾收集机制的语言编写程序,开发人员一般不必操心内存管理的问题。但是,JavaScript
  在进行内存管理及垃圾收集时面临的问题还是有点与众不同。其中最主要的一个问题,就是分配给 Web
  浏览器的可用内存数量通常要比分配给桌面应用程序的少。这样做的目的主要是出于安全方面的考虑,
  目的是防止运行 JavaScript 的网页耗尽全部系统内存而导致系统崩溃。内存限制问题不仅会影响给变量
  分配内存,同时还会影响调用栈以及在一个线程中能够同时执行的语句数量。

  因此,确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行
  中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为 null 来释放其引用——这个
  做法叫做解除引用(dereferencing)。这一做法适用于大多数全局变量和全局对象的属性。局部变量会在
  它们离开执行环境时自动被解除引用,如下面这个例子所示:


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


  在这个例子中,变量 globalPerson 取得了 createPerson() 函数返回的值。在 createPerson()
  函数内部,我们创建了一个对象并将其赋给局部变量 localPerson ,然后又为该对象添加了一个名为
  name 的属性。最后,当调用这个函数时, localPerson 以函数值的形式返回并赋给全局变量
  globalPerson 。由于 localPerson 在 createPerson() 函数执行完毕后就离开了其执行环境,因此
  无需我们显式地去为它解除引用。但是对于全局变量 globalPerson 而言,则需要我们在不使用它的
  时候手工为它解除引用,这也正是上面例子中最后一行代码的目的。
  
  不过,解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离
  执行环境,以便垃圾收集器下次运行时将其回收。

总结


    JavaScript 是一门具有自动垃圾收集机制的编程语言,开发人员不必关心内存分配和回收问题。可
    以对 JavaScript 的垃圾收集例程作如下总结:
    
    1、离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除。

    2、“标记清除”是目前主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,然
       后再回收其内存。

    3、另一种垃圾收集算法是“引用计数”,这种算法的思想是跟踪记录所有值被引用的次数。JavaScript
       引擎目前都不再使用这种算法;但在 IE 中访问非原生 JavaScript 对象(如 DOM 元素)时,这种
       算法仍然可能会导致问题。

    4、当代码中存在循环引用现象时,“引用计数”算法就会导致问题。

    5、解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效地回
       收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用。
    

推荐:js垃圾回收机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值