前言
垃圾回收是一个老生常谈的话题。不同的语言有不同的垃圾回收机制,像C、C++采用的是手动回收机制,而JavaScript、Java等语言采用的是自动回收机制。
自动回收也就是说,产生的垃圾由垃圾回收器处理,开发人员不需要关心。垃圾回收器会按照固定的时间间隔,周期性地进行垃圾回收。
标记清除
垃圾回收一般有两种方式:标记清除和引用计数。目前主流浏览器采用的都是标记清除(但垃圾收集的时间间隔不同),引用计数不常用。
这里有两个概念,新生代 & 老生代。
新生代
新生代会存放生存时间较短的对象,1-8M,副垃圾回收器来负责。
新生代采用Scavenge算法,把空间平分成对象区域和空闲区域,对象区域快存满了,就标记一下存活的对象,移动到空闲区域,然后空闲区域就变成了对象区域,原对象区域就变成了空闲区域。以此循环。
发现了没?这套算法有一个好处:不会产生内存碎片,每次移动存活对象的时候,自动就对内存进行整理了。
新生代两次垃圾回收后还存在的对象,会被放入老生代。
老生代
老生代空间大,会存放生存时间较长的对象,由主垃圾回收器负责。
老生代是如何回收垃圾的呢?
- 标记
从一组根元素开始,递归遍历他们的引用,可以到达的对象就是存活的对象,否则就是垃圾数据。
(图片截取自前端小智的文章:前端面试:谈谈 JS 垃圾回收机制 侵删)
- 回收
垃圾回收器回收垃圾数据的内存。
3. 内存整理
这一步非必须,当多次垃圾回收之后,内存中就会出现大量的内存碎片,内存空间不连续,遇到大的对象可能就无法进行内存分配,这时候就需要内存整理了。
引用计数
还有一种垃圾回收策略是引用计数(不常用)。引用计数就是跟踪记录每个值被引用的次数。
var a = { x: 5 };
var b = a;
这块内存的引用次数就是2。
当对象a和对象b的指向都改变时,这块内存的引用次数为0,就可以被垃圾回收器回收了。 但是引用计数有一个很严重的问题:循环引用。
function fun() {
var obj1 = {};
var obj2 = {};
obj1.x = obj2;
obj2.x = obj1;
}
例如这段代码,obj1
和obj2
指向的内存区域引用计数都为2,到函数执行完,本该释放内存,但是他们的引用计数不为0,垃圾回收器无法回收,就会导致内存泄露。
引用计数并不常用,了解一下即可。
手动垃圾回收
虽说JavaScript是垃圾回收是自动的,但是也有手动回收的办法,但是不建议大家这么做。
- IE中,调用
window.CollectGarbage()
方法会立即执行垃圾回收。 - Opera7以及更高版本中,调用
window.opera.collect()
也会启动垃圾收集。
也可以看看这篇如何手动触发 JavaScript 垃圾回收行为?
参考
《JavaScript高级程序设计》
最后
以上,如有错漏,恳请指正!