为什么需要垃圾回收
由于字符串、对象和数组没有固定大小,只有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。
JS自身拥有垃圾回收机制
在C/C++语言中,开发人员的一项基本工作任务就是手动追踪内存的使用情况,而js开发人员则无需关心这一问题,所需内存的分配及无用内存的回收完全实现了自动管理
原理
垃圾收集器会找出那些不再继续使用的变量,然后释放其内存,为此,垃圾收集器会按照固定的时间间隔(或者代码中预定的收集时间),周期性的执行这一操作。
函数内局部变量的正常生命周期
局部变量只在函数执行的过程中存在.在这个过程中,会为局部变量在栈(基本类型)或者堆 (引用类型)分配内存地址,直到函数执行结束,局部变量也就没有存在的意义,这种情况下,很容易判断变量是否还有存在的意义,但是并非所有的情况下都那么容易得出结论,比如闭包.
垃圾收集器必须跟踪哪个变量有用哪个没用,对于不再有用的变量打上标记,以便将来回收其占用的资源,用于标记无用变量的策略可能会因实现而异,但具体到浏览器上的实现通常有两个策略.
-
标记清除
js中最常用的垃圾收集方式就是<strong>标记清除</strong>.
当变量进入环境: 例如在函数中声明一个变量, 就将这个变量标记为进入环境
function f () {
var name = 'jack' // name 进入环境
}
从逻辑上讲,永远不能释放进入环境的变量所占用的内存地址,因为只要执行流进入相应的环境,就可能会用到他们。而当变量离开环境时,则将其标记为离开环境。
如何标记变量其实并不重要,关键在于使用什么策略,垃圾收集器在运行时会给内存中的所有变量加上标记,然后去掉那些环境中的变量以及被环境引用的变量,剩下的再加上标记的就视为要删除的变量,原因是环境中以及无法使用这些变量了,最后垃圾收集器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存
-
引用计数
不太常见的一种垃圾回收策略,因为会导致一系列的问题.
引用计数的含义是跟踪记录每 个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是 1。 如果同一个值又被赋给另一个变量,则该值的引用次数加 1。相反,如果包含对这个值引用的变量又取 得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这 个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那 些引用次数为零的值所占用的内存。
Netscape Navigator 3.0 是最早使用引用计数策略的浏览器,但很快它就遇到了一个严重的问题: <strong>循环引用</strong> 。循环引用指的是对象 A 中包含一个指向对象 B 的指针,而对象 B 中也包含一个指向对象 A 的 引用。请看下面这个例子:
function problem(){
var objectA = new Object();
var objectB = new Object();
objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}
在函数内创建两个对象 并且相互引用 这样永远不会被垃圾收集器回收.
Netscape 在 Navigator 4.0 中放弃了引用计数方式,转而采用标记清除来实现其垃圾收 集机制。可是,引用计数导致的麻烦并未就此终结。
在ie早期的版本 (9之前) 部分对象并不是原生JavaScript对象 比如 dom 和 bom使用的是c++的组合对象模型(COM)对象的方式实现的,即使ie浏览器使用的是标记清除策略,但c++的垃圾回收确实引用计数,所以实质上还是使用的引用计数,这会导致一系列的问题 。
至此 在IE9开始把DOM和BOM都转成了真正的js对象,这样,就避免了 两种垃圾收集算法并存导致的问题,也消除了常见的内存泄漏现象。