JavaScript垃圾回收机制

什么是垃圾回收机制?

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

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

垃圾回收机制回收的是什么?

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

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

var global = "I'm global";
function test(){
    var local = “I m local” ;
}
test();

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

什么时候触发垃圾回收

垃圾回收器周期性运行,如果分配的内存非常多,那么回收工作也会很艰巨,确定垃圾回收时间间隔就变成了一个值得思考的问题。IE6的垃圾回收是根据内存分配量运行的,当环境中存在256个变量,4096个对象,64k的字符串任意一种情况的时候就会触发垃圾回收器工作,看起来很科学,不用按一段时间就调用一次,但是如果环境中就是有这么多变量等一直存在,现在脚本如此复杂,很正常,那么结果就是垃圾回收器一直在工作,这样浏览器就没法儿玩儿了。

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

js的两种回收机制

标记清除(mark and sweep)

从语义上理解就比较好理解了,就是当变量进入到某个环境中的时候就把这个变量标记一下,比如标记为“进入环境”,当离开的时候就把这个变量的标记给清除掉,比如是“离开环境”,而在这后面还有标记的变量将被视为准备删除的变量。

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

这是javascript最常见的垃圾回收方式。至于上面有说到的标记,到底该如何标记?好像是有很多方法,比如特殊位翻转,维护一个列表什么的。

工作原理:是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
工作流程:

  1. 垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。
  2. 去掉环境中的变量以及被环境中的变量引用的变量的标记。
  3. 再被加上标记的会被视为准备删除的变量。
  4. 垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。
引用计数(reference counting)

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

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

但是刚刚也说了,第一种标记清除是最经常用到的,那么这个看起来这么好的引用计数为啥不被别人用了呢?
工作原理:跟踪记录每个值被引用的次数。
工作流程:

  1. 声明了一个变量并将一个引用类型的值赋值给这个变量,这个引用类型值的引用次数就是1。
  2. 同一个值又被赋值给另一个变量,这个引用类型值的引用次数加1.
  3. 当包含这个引用类型值的变量又被赋值成另一个值了,那么这个引用类型值的引用次数减1.
  4. 当引用次数变成0时,说明没办法访问这个值了。
  5. 当垃圾收集器下一次运行时,它就会释放引用次数是0的值所占的内存。
循环引用时的问题

但是循环引用的时候就会释放不掉内存。循环引用就是对象A中包含另一个指向对象B的指针,B中也包含一个指向A的引用。因为IE中的BOM、DOM的实现使用了COM,而COM对象使用的垃圾收集机制是引用计数策略。所以会存在循环引用的问题。

解决:手工断开js对象和DOM之间的链接。赋值为null。IE9把DOM和BOM转换成真正的JS对象了,所以避免了这个问题因为这个过程中会出现一个循环引用的问题!简单点来说就是一个对象小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引擎是用的标记清除来实现的,但是js访问COM对象如BOM,DOM还是基于引用计数的策略的,也就是说只要在IE中涉及到COM对象,也就会存在循环引用的问题。比如说第一种情况:一个DOM元素和一个原生的js对象之间的循环引用

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

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

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

上面这个代码创建了一个作为ele元素处理程序的闭包,而这个闭包则又创建了一个循环引用。匿名函数中保存了一个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意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存。

垃圾回收机制的好处和坏处

好处:大幅简化程序的内存管理代码,减轻程序猿负担,并且减少因为长时间运转而带来的内存泄露问题。
坏处:自动回收意味着程序猿无法掌控内存。ECMAScript中没有暴露垃圾回收的借口,我们无法强迫其进行垃圾回收,更加无法干预内存管理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 和C#、Java一样JavaScript有自动垃圾回收机制,也就是说执行环境会负责管理代码执行过程中使用的内存,在...
    ConRon阅读 289评论 0 0
  • 为什么需要垃圾回收 由于字符串、对象和数组没有固定大小,只有当他们的大小已知时,才能对他们进行动态的存储分配。Ja...
    宇cccc阅读 1,045评论 1 0
  • 「杜思仲说」 我叫杜思仲。 三天前,我的女友失踪了。临走前,她告诉我,其实她是一朵修炼了六百年的桃花精,为了来人间...
    夏初启阅读 1,461评论 16 11
  • 有多少没有雨的日子 能想起你 你的笑 是一轮明月 弯弯地 像一叶舟 从我的心里漂过 有多少温暖的日子 捧着一杯茶 ...
    诗墨闻香阅读 123评论 3 2
  • 小天的母亲病了,躺在病床上。 其实母亲的主治医生在一个礼拜前已经告知小天,母亲时日已不多,让小天以及其所有的家人做...
    小朱小文阅读 646评论 0 1