javascript内存回收机制

来源:互联网 发布:视频剪切制作软件 编辑:程序博客网 时间:2024/05/18 01:24

1.碎碎念

讲到了javascript变量,在上一篇中蜻蜓点水地提到了变量的内存分配,但是对内存的回收却没有详细的说明,这是我的一大疏忽。今天就专门来聊聊javascript的内存回收机制。javascript语言是一门优秀的脚本语言.其中包含脚本语言的灵活性外还拥有许多高级语言的特性.例如充许构建和实例化一个对象,垃圾回收机制(GC:Garbage Collecation)

2.初识GC

javascript和C#、Java一样JavaScript有自动垃圾回收机制,也就是说执行环境会负责管理代码执行过程中使用的内存,在开发过程中就无需考虑内存分配及无用内存的回收问题。JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是时时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。

通常我们使用new创建对象,GC负责回收对象占用内存区域.因此了解GC,可以加深对javascript垃圾回收机制的理解。

垃圾回收原理:

找出内存中不再继续使用的变量,将其所占用的内存释放,开发人员不再关心内存的使用情况,内存的分配已经无用内存的释放完全实现了自动化管理.

变量生命周期

这里很多人会困惑,什么叫不再继续使用的变量?不再继续使用的变量就是生命周期结束的变量,当然只可能是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。局部变量只在函数的执行过程中存在,而在这个过程中会为局部变量在栈或堆上分配相应的空间,以存储它们的值,然后再函数中使用这些变量,直至函数结束(闭包中由于内部函数的原因,外部函数并不能算是结束)

  1. <SCRIPT LANGUAGE="JavaScript">  
  2.       <!--  
  3.     //猫  
  4.     function cat(name){  
  5.         var zhuren ;  
  6.         this.name = name;  
  7.           
  8.         //设置主人  
  9.         this.addZhuRen = function(zr){  
  10.             zhuren = zr;  
  11.         }  
  12.  
  13.         this.getZhuRen = function(){  
  14.             return zhuren;  
  15.         }  
  16.     }  
  17.       
  18.     //主人  
  19.     function zhuren(name){  
  20.         this.name = name;  
  21.     }  
  22.  
  23.     //创建主人:  
  24.     var zr = new zhuren("zhangsan");  
  25.     //创建猫  
  26.     var cat1 = new cat("asan");  
  27.     //设置该猫的主人  
  28.     cat1.addZhuRen(zr);  
  29.     //释放主人  
  30.     zr = null ;  
  31.     //此处还存在对主人对象的引用  
  32.     alert(cat1.getZhuRen().name)  
  33.       //-->  
  34.   </SCRIPT>
)。

一旦函数结束,局部变量就没有存在必要了,可以释放它们占用的内存。貌似很简单的工作,为什么会有很大开销呢?这仅仅是垃圾回收的冰山一角,就像刚刚提到的闭包,貌似函数结束了,其实还没有,垃圾回收器必须知道哪个变量有用,哪个变量没用,对于不再有用的变量打上标记,以备将来回收。用于标记无用的策略有很多,常见的有两种方法。

垃圾回收的方法有2种: (1)标记清除 (2)引用计数

标记清除(mark and sweep):

目前javascript最常用的一种垃圾回收机制.

当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”。垃圾收集器在运行时会给存储在内存中的所有变量都加上一个标记,然后检查哪些变量还在环境中使用,将去掉这些变量的标记,检查哪些变量被环境中的变量说引用,也去掉这些变量的标记,最后剩下的这些带有标记的变量将会是"准备被清理的变量"。至于怎么标记有很多种方式,比如特殊位的反转、维护一个列表等,这些并不重要,重要的是使用什么策略,原则上讲不能够释放进入环境的变量所占的内存,它们随时可能会被调用的到。

垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了,因为环境中的变量已经无法访问到这些变量了,然后垃圾回收器相会这些带有标记的变量机器所占空间。

大部分浏览器都是使用这种方式进行垃圾回收,区别在于如何标记及垃圾回收间隔而已,只有低版本IE使用的引用计数。

引用计数(reference counting):

这是一种不太常用的垃圾回收机制.

在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。

看起来也不错的方式,为什么很少有浏览器采用,还会带来内存泄露问题呢?主要是因为这种方式没办法解决循环引用问题。比如对象A有一个属性指向对象B,而对象B也有有一个属性指向对象A,每个对象的ref属性引用另外一个对象,如下代码

var A=new Object();var B=new Object();A.ref=B;B.ref=A;
这样的相互引用,导致A,B俩个引用计数都是1,永远没有变成0的时候,这时垃圾收集器将永远不会对其进行清理.

既然提到了IE,我们在多聊点。

在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的,也就是说只要涉及BOM及DOM就会出现循环引用问题。看上面的例子,有人会说,这肯定是个菜鸟,高手不会做这么无聊的事情,其实非也!看一下这段代码:

window.onload=function outerFunction(){        var obj = document.getElementById("element");        obj.onclick=function innerFunction(){};    };
这段代码看起来没什么问题,但是obj引用了document.getElementById(“element”),而document.getElementById(“element”)的onclick方法会引用外部环境中的变量,自然也包括obj,是不是很隐蔽啊。

解决办法

最简单的方式就是自己手工解除循环引用,比如刚才的函数可以这样

window.onload=function outerFunction(){       var obj = document.getElementById("element");       obj.onclick=function innerFunction(){};       obj=null; //没有用的对象及时赋值为null    };
什么时候触发垃圾回收?

垃圾回收器周期性运行,如果分配的内存非常多,那么回收工作也会很艰巨,确定垃圾回收时间间隔就变成了一个值得思考的问题。

IE6的垃圾回收是根据内存分配量运行的,当环境中存在256个变量、4096个对象、64k的字符串任意一种情况的时候就会触发垃圾回收器工作,看起来很科学,不用按一段时间就调用一次,有时候会没必要,这样按需调用不是很好吗?但是如果环境中就是有这么多变量等一直存在,现在脚本如此复杂,很正常,那么结果就是垃圾回收器一直在工作,这样浏览器就没法玩了。

微软在IE7中做了调整,触发条件不再是固定的,而是动态修改的,初始值和IE6相同,如果垃圾回收器回收的内存分配量低于程序占用内存的15%,说明大部分内存不可被回收,设的垃圾回收触发条件过于敏感,这时候把临街条件翻倍,如果回收的内存高于85%,说明大部分内存早就该清理了,这时候把触发条件置回。这样就使垃圾回收工作职能了很多。

同C# 、Java一样我们可以手工调用垃圾回收程序,比如IE浏览器中 ,调用 windows.CollectGarbage() 或者opera7及更高版本,调用 window.opera.collect()也可以启动垃圾回收器.但是由于其消耗大量资源,而且我们手工调用的不会比浏览器判断的准确,所以不推荐手工调用垃圾回收。

为了更好,更有效的使用内存,我们建议没有用的对象及时赋值null,这样对象将会快被垃圾收集器处理.

 3.深度解析GC

1)用局部变量和全局变量解释GC

GC在回收内存时,首先会判断该对象是否被其它对象引用.在确定没有其它对象引用便释放该对象内存区域.因此如何确定对象不再被引用是GC的关键所在.

    <script>              function aa(){                  this.rr = "testA";                  }              function bb(){                  this.rr = testB";              }              var b1;              function cc(){                  var a1 = new aa();                  b1 = new bb();                  return b1;              }              cc();              alert(b1.rr)          </script> 

如上代码中,执行完cc()后a1被回收了,此后我们可以通过b1.rr弹出"testB".在一些基础书籍中解释为:a1为局部变量,b1是全局变量.局部

变量执行完后会被GC回收.但不全是这样,如下代码:

    <script>              function aa(){                  this.rr = "testA";                  }              function bb(){                  this.rr = "testB";              }              function cc(){                  var a1 = new aa();                  var b1 = new bb();                      return b1;              }              var b1 = cc();              alert(b1.rr);          </script> 
此时cc函数中的 a1,b1都是局部变量,但仍然会弹出"testB".说明b1并没有被GC回收.因此JavaScript中局部变量不是所有时候都被GC回收的.

这个例子的编译时的内存分配图比较简单,有兴趣画一画的读者可以参照javascript变量之作用域。

下面我们再举个例子深入理解.

局部变量在函数返回以后,并不一定会被立即回收,而是会等待不再被引用的时候,才会被回收!下面局部变量i在函数test返回以后,仍然存在,因为有m一直引用着该数组。但一旦手动将 m = null,该数组将因为无引用被自动回收。
function test(){    var i = [1,2];    return i;  }  var m = test();  alert(m);//1,2  

2)抽象理解GC

我们再进一步里了解一下GC回收机制,先引入几个概念:双向链表,作用域链,活动对象), 其中双向链表描述复杂对象的上下层级关系. 作用域链与活动对象分别是双向链表中的某个节点.承接第一个例子,以函数cc为例变量层级关系为:

在执行cc()方法时,内存中变量的引用关系如下:

window<=>cc<=>a1<=>rr<=>b1<=>rr 

window的活动对象包括cc,假设window是顶级对象(因为运行中不会被回收)

cc的活动对象包括a1和b1,其作用域链是window

a1的活动对象包括rr,其作用域链是cc

b1的活动对象包括rr,其作用域链是cc

执行cc()时,cc的执行环境会创建一个活动对象和一个作用域链.其局部变量a1,b1都会挂在cc的活动对象中.当cc()执行完毕后,执行环境

会尝试回收活动对象占用的内存.但因局部变量b1 通过return b1,为其增加了一条作用域链:window<=>b1<=>rr,所以GC停止对b1回收.

因此如果想将一个局部变量/函数提升为全局的,为其增加一条作用域链就OK了。

同时控制好对象的作用域链也变得重要了.因作用域链会意外导致GC无法回收目标对象.这里引入一个经典的例子:

<script>       function cat(name){          var catHost;          this.name = name;          this.addCatHost = function(host){              catHost = host;          }          this.getCatHost = function(){              return catHost;          }      }      function Host(name){          this.name = name;      }        var host = new Host("freedom");      var cat = new cat("Lucy");     cat.addCatHost(host);     host = null ;      alert(cat.getCatHost().name)  </script> <span></span>

结果:弹出“freedom”






0 0
原创粉丝点击