JavaScript垃圾收集——标记清除和引用计数
http://blog.csdn.net/yuezhiren/article/details/7948950
http://blog.csdn.net/zhouziyu2011/article/details/61201613
翻译自 : http://www.brpreiss.com/books/opus5/html/page424.html , 用于了解用于垃圾回收的标记清除算法.
标记清除算法是一种垃圾回收算法,它是第一个可以回收被循环引用的数据结构的垃圾回收算法.现在仍旧有许多常用的垃圾回收技术使用各种各样的标记清除算法的变体.
在使用标记清除算法时,未引用对象并不会被立即回收.取而代之的做法是,垃圾对象将一直累计到内存耗尽为止.当内存耗尽时,程序将会被挂起,垃圾回收开始执行.当所有的未引用对象被清理完毕时,程序才会继续执行.
标记清除算法又被叫做追踪式垃圾收集器,这是因为这种算法追踪被程序所引用的所有对象.在程序中可以直接访问的对象是指通过堆栈上的本地变量或者任意静态变量说引用的对象.从垃圾回收的角度来看,这种对象,叫做根(roots).如果一个对象是被另外的可(直接或者间接)访问的对象中的域引用,则这个对象可以被间接访问.可访问的对象被称为可用的(live),其他的对象被称为垃圾(garbage).
标记清除算法由两个阶段组成:
第一阶段,标记所有的可访问对象.第一阶段叫做标记阶段.
第二阶段,垃圾收集算法扫描堆并回收所有的未标记对象.第二阶段叫做收集阶段.
算法伪码如下:
for each root variable r
mark (r);
sweep ();
为了区分可用对象与垃圾对象,我们需要在每一个对象中记录对象的状态.因此,我们在每一个对象中加入了一个特殊的布尔类型的域,叫做marked.默认情况下,对象被创建时处于未标记状态.所以,marked域被初始化为false.
对象p以及所有间接引用对象p的对象可以被下面的递归标记算法所标记:
void mark (Object p)
if (!p.marked)
p.marked = true; for each Object q referenced by p mark (q);
在第二阶段,标记清除算法扫描堆上的所有对象,主要是为了定位所有未被标记的对象.分配给未被标记(即marked=false)的对象的存储空间将会被回收.同时,可用对象的marked域将会被设置回false,以为下一次标记清除算法时做准备.
void sweep ()
for each Object p in the heap
if (p.marked) p.marked = false else heap.release (p);
下面的图示解释了标记清除算法所执行的操作.图示(a)为垃圾回收执行之前的状态.在这个例子中,是一个单一根变量.图示(b)为执行mark阶段之后的状态.在这个阶段,所有可用的对象都被标记了.最后,在图示(c)中,被标记为false的对象被清理了,可用的对象(live)留了下来,并且所有可用(live)对象的marked域都被置成了false.

图示 : 标记清除式的垃圾回收
因为标记清除式的垃圾回收跟踪了由根(root)访问的所有对象,所以即使是在有循环引用时,它也可以正确的标记并执行垃圾回收工作.这是标记清除式垃圾回收与前一章所说的引用计数式垃圾回收相比,最主要的一个优势.标记清除的第二个优点是,对于引用对象的常规操作不会产生任何的额外开销.
标记清除方法的主要缺点是,当垃圾回收算法执行时,正常的程序会被挂起.特别是,如果一个程序是交互式程序或者正在有一些实时运算时,这就会成为一个问题.比如,一个正在进行垃圾回收的交互式程序会周期的无响应.
JavaScript具有自动垃圾收集机制,执行环境会负责管理代码执行过程中使用的内存。也就是说,所需内存的分配和无用内存的回收完全实现了自动管理。
垃圾回收机制的原理:垃圾收集器会按照固定的时间间隔或代码执行中预定的收集时间,周期性地执行以下操作——找出不再继续使用的变量,然后释放其占用的内存。
用于标识无用变量的方式有两种:标记清除法和引用计数法。
1、标记清除法:
javascript最常用的垃圾收集方式。当变量进入环境时,这个变量标记为“进入环境”;而当变量离开环境时,则将其标记为“离开环境”。可以使用一个“进入环境”的变量列表及一个“离开环境”的变量列表来跟踪变量的变化,也可以翻转某个特殊的位来记录一个变量何时进入环境及离开环境。
2、引用计数法:
不太常见的垃圾收集策略。引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则该值的引用次数就是1;如果同一个值又被赋给另一个变量,则该值的引用次数加1;如果包含对该值引用的变量又取得了另外一个值,则该值的引用次数减1。当该值的引用次数变为0时,则可以回收其占用的内存空间。当垃圾回收器下一次运行时,就会释放那些引用次数为0的值所占用的内存。
问题:循环引用。
对象A包含一个指向对象B的指针,而对象B中也包含一个指向A的指针。
- function obj() {
- var objA = new Object();
- var objB = new Object();
- objA.propertyA = objB;
- objB.propertyA = objA;
- <span style="font-size:14px;">}</span>
objA和objB的引用次数都是2,采用标记清除策略时,函数执行后,两个对象都离开了作用域,因此相互引用不是问题,但采用引用计数策略时,函数执行后,两个对象还将继续存在,因为它们的引用次数永远不会为0。
因此一般不推荐用引用计数法,推荐使用标记清除法。
然而,IE中有一部分对象并不是原生JavaScript对象,如BOM和DOM对象就是使用C++以COM(组件对象模型)对象的形式实现的,而COM对象的垃圾收集机制采用的就是引用计数策略,因此,即使IE的JavaScript引擎是使用标记清除策略实现的,但JavaScript访问的COM对象依然是基于引用计数策略的,也即是说,只要IE中涉及COM对象,就会存在循环引用的问题。
- var element = document.getElementById("element");
- var obj = new Object();
- obj.property1 = element;
- element.property2 = obj;
解决:手动断开两个对象之间的相互引用。
在使用完毕后,手动断开原生JavaScript对象与DOM元素之间的连接,消除循环引用。当垃圾回收器下一次运行时,就会会删除这些值并回收它们占用的内存。
- obj.property1 = null;
- element.property2 = null;
解除引用:
一旦数据不再有用,最好通过将其设置为null来释放其引用。
然而,解除一个值的引用不意味着自动回收该值所占用的内存,解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。
浙公网安备 33010602011771号