浅谈JavaScript垃圾回收机制

807 阅读3分钟

前言

垃圾回收是一个老生常谈的话题。不同的语言有不同的垃圾回收机制,像C、C++采用的是手动回收机制,而JavaScript、Java等语言采用的是自动回收机制。

自动回收也就是说,产生的垃圾由垃圾回收器处理,开发人员不需要关心。垃圾回收器会按照固定的时间间隔,周期性地进行垃圾回收。

标记清除

垃圾回收一般有两种方式:标记清除和引用计数。目前主流浏览器采用的都是标记清除(但垃圾收集的时间间隔不同),引用计数不常用。

这里有两个概念,新生代 & 老生代

新生代

新生代会存放生存时间较短的对象,1-8M,副垃圾回收器来负责。

新生代采用Scavenge算法,把空间平分成对象区域和空闲区域,对象区域快存满了,就标记一下存活的对象,移动到空闲区域,然后空闲区域就变成了对象区域,原对象区域就变成了空闲区域。以此循环。

发现了没?这套算法有一个好处:不会产生内存碎片,每次移动存活对象的时候,自动就对内存进行整理了。

新生代两次垃圾回收后还存在的对象,会被放入老生代。

老生代

老生代空间大,会存放生存时间较长的对象,由主垃圾回收器负责。

老生代是如何回收垃圾的呢?

  1. 标记
    从一组根元素开始,递归遍历他们的引用,可以到达的对象就是存活的对象,否则就是垃圾数据。

(图片截取自前端小智的文章:前端面试:谈谈 JS 垃圾回收机制 侵删)

  1. 回收
    垃圾回收器回收垃圾数据的内存。

3. 内存整理
这一步非必须,当多次垃圾回收之后,内存中就会出现大量的内存碎片,内存空间不连续,遇到大的对象可能就无法进行内存分配,这时候就需要内存整理了。

引用计数

还有一种垃圾回收策略是引用计数(不常用)。引用计数就是跟踪记录每个值被引用的次数。

var a = { x: 5 };
var b = a;

这块内存的引用次数就是2。

当对象a和对象b的指向都改变时,这块内存的引用次数为0,就可以被垃圾回收器回收了。 但是引用计数有一个很严重的问题:循环引用。

function fun() {

    var obj1 = {};
    var obj2 = {};

    obj1.x = obj2;
    obj2.x = obj1;
}

例如这段代码,obj1obj2指向的内存区域引用计数都为2,到函数执行完,本该释放内存,但是他们的引用计数不为0,垃圾回收器无法回收,就会导致内存泄露。

引用计数并不常用,了解一下即可。

手动垃圾回收

虽说JavaScript是垃圾回收是自动的,但是也有手动回收的办法,但是不建议大家这么做

  • IE中,调用window.CollectGarbage()方法会立即执行垃圾回收。
  • Opera7以及更高版本中,调用window.opera.collect()也会启动垃圾收集。

也可以看看这篇如何手动触发 JavaScript 垃圾回收行为?

参考

《JavaScript高级程序设计》

最后

以上,如有错漏,恳请指正!