javascript的垃圾回收机制

责编:menVScode 2018-01-18 22:45 阅读(569)

        和java,c#一样,javascript也有垃圾回收的机制,比如说c++和c就没有垃圾回收机制。可能有这么一种倾向,垃圾回收机制必须有一种平台来进行回收。比如说下面将的javascript的执行环境V8就会负责管理代码执行过程中的垃圾回收。

        javascript具有自动垃圾回收机制,执行环境会负责管理代码执行过程中使用的内存。原理就是找出那些不再继续使用的变量,然后释放其占有内存。这整个过程也会按照一个固定的事件周期性的整形(时时的话开销太大)。

        变量的声明周期

        刚刚原理中提到要找出不再使用的变量,什么是不再使用的对象呢?不再使用的变量也就是生命周期结束的变量。目前javascript有两种变量,全局变量和在函数中产生的局部变量(暂不考虑ES6中的块级作用域)。

        全局变量的声明周期一直持续到浏览器关闭页面才会清除,而局部变量只是在函数执行器存在,而在这个过程中会为局部变量在栈或者堆上分配相应的空间,来存储他们的值,然后当函数要使用这些变量的值时再取出来使用。一直到函数结束(闭包会不同)。

        一旦函数结束,局部变量就不需要了,这时候就可以释放他们的内存。

var globalVariable = "I'm global";
function test(){
    var localVariable = "I'm local";
}
test();

        这个例子里面,global在关闭浏览器时释放,local在函数test结束后,释放。

        js的两种回收机制

        (1) 标记清除(mark and sweep)

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

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

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

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

        (2) 引用计数(reference counting)

        引用计数的含义是跟踪记录每个值被引用的次数,当声明一个变量并将一个引用类型的值赋给该变量时,这个时候的引用类型的值就会是引用次数+1了。如果同一个值又被赋给另外一个变量,则该值的引用次数又+1。

        相反如果包含这个值的引用的变量又取得另外一个值,即被重新赋了值,那么这个值的引用就减一。当这个值的引用次数编程0时,表示没有用到这个值,这个值也无法访问,因此环境就会收回这个值所占用的内存空间回收。这样,当垃圾收集器下次再运行时,它就会释放引用次数为0的值所占用的内存。

        Netscape Navigator3是最早使用引用计数策略的浏览器,但很快它就遇到一个严重的问题:循环引用。简单点来说就是一个对象小a的属性,引用了对象小b。小b对象也有一个属性引用了小a,那么小a,小b互相引用对方,也就造成了循环引用的问题啦。

function test(){
    var a = {};
    var b = {};
    a.property = b;
    b.property = a;
}

        这就是一个很明显的循环引用了,小a和小b通过各自的属性互相引用,导致了内存无法释放。(有那么一点点的感觉像死锁。。。。)即使是再test()执行完后,如果使用标记清除是没有问题的,离开环境的时候就会被清除。但是引用计数不行,因为这两个对象的引用次数还是存在,不会变成0,所以其占用空间也不会清理,如果这个函数被调用多次,就会不断有内存被占用。造成了内存泄露。

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

        我们知道,IE中有一部分对象并不是原生js对象。例如,其DOM和BOM中的对象就是使用C++以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数策略。因此,即使IE的js引擎采用标记清除策略来实现,但js访问的COM对象依然是基于引用计数策略的。换句话说,只要在IE中涉及COM对象,就会存在循环引用的问题。

        (1) 比如说第一种情况:一个DOM元素和一个原生的js对象之间的循环引用

var ele = document.getElementById("ele");
var obj = {};
obj.property = ele;
ele.property = obj;
//这种情况应该手动设置,在不适用的时候手工断开js和dom元素之间的连接
obj.property = null;
ele.property = null;

        (2) 比如第二种情况是闭包的作用域链中包含着一个html元素,那么这个元素无法被销毁

window.onload = function outerFunction(){
    var ele= document.getElementById("element");
    ele.onclick = function (){
        console.log(ele.id);
    }
}

        上面这个代码创建了一个作为ele元素处理程序的闭包,而这个闭包则又创建了一个循环引用。y匿名函数中保存了一个outerFunction()的活动对象的引用,因此就会导致无法减少ele的引用。可以改成下面这个:

window.onload = function outerFunction(){
    var ele= document.getElementById("element");
    var id = ele.id;
    ele.onclick = function (){
        console.log(id);
    }
    ele = null;
}

        在上面的代码中,通过把ele.id 的一个副本保存在一个变量中,并且在闭包中引用改变量消除了循环引用。

        必须要记住:闭包会引用包含函数的整个活动对象,而其中包含着elem。即使闭包不直接引用ele(比如上面的例子我们不用id),包含函数的多动对象中也依旧会保存一个引用。因此,有必要把ele变量设置为null。这样就能够解除对DOM对象的引用,顺利地减少其引用数,确保正常回收其占用的内存。

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

        为了解决上述问题,IE9把BOM和DOM对象都转换成了真正的JavaScript对象。

        

        javascript与V8引擎

        (1) 垃圾回收机制的好处和坏处

        好处:大幅简化程序的内存管理代码,减轻程序猿负担,并且减少因为长时间运转而带来的内存泄露问题。

        坏处:自动回收意味着程序猿无法掌控内存。ECMAScript中没有暴露垃圾回收的借口,我们无法强迫其进行垃圾回收,更加无法干预内存管理。

        (2) node内存管理问题

        在浏览器中,V8引擎实例的生命周期不会很长(因为我们使用完网站就会把网站关闭),而且运行在用户的机器上。如果不幸发生内存泄露等问题,仅仅会影响到一个终端用户。且无论这个V8实例占用了多少内存,最终在关闭页面时内存都会被释放,几乎没有太多管理的必要(当然并不代表一些大型Web应用不需要管理内存)。但如果使用Node作为服务器,就需要关注内存问题了,一旦内存发生泄漏,久而久之整个服务将会瘫痪(服务器不会频繁的重启)。

        (3) 涨知识之V8内存限制

        Node与其他语言不同的一个地方,就是其限制了JavaScript所能使用的内存(64位为1.4GB,32位为0.7GB),这也就意味着将无法直接操作一些大内存对象。这很令人匪夷所思,因为很少有其他语言会限制内存的使用。

        V8之所以限制了内存的大小,表面上的原因是V8最初是作为浏览器的JavaScript引擎而设计,不太可能遇到大量内存的场景,而深层次的原因则是由于V8的垃圾回收机制的限制。由于V8需要保证JavaScript应用逻辑与垃圾回收器所看到的不一样,V8在执行垃圾回收时会阻塞JavaScript应用逻辑,直到垃圾回收结束再重新执行JavaScript应用逻辑,这种行为被称为“全停顿”(stop-the-world)。若V8的堆内存为1.5GB,V8做一次小的垃圾回收需要50ms以上,做一次非增量式的垃圾回收甚至要1秒以上。这样浏览器将在1s内失去对用户的响应,造成假死现象。如果有动画效果的话,动画的展现也将显著受到影响。

前端交流群: MVC前端网(menvscode.com)-qq交流群:551903636

邮箱快速注册

忘记密码