标记清除算法是第一个能够回收循环数据结构的算法。现在最普遍的垃圾回收技术都是标记清除算法的变体。当使用标记清除算法,没有被引用的对象不会被立即回收。相反,垃圾回收器允许先累计一些没有任何引用的对象 到一定程度(比如内存不足时),然后启动回收。在回收过程中,执行的程序会被临时挂起。一旦回收结束,程序又能恢复执行。
标记清除算法又称为 根跟踪算法root tracing . 在JVM 中,GC Roots 包括:
A:在VM 栈中,由本地变量引用的对象。
B:在方法去中的静态引用。
C:JNI(即native 方法)中的引用
从上述Roots 开始,能够访问到的对象称为LIVE 对象,访问不到的对象称为not live.标记清除算法包含两个阶段: 阶段1:找到并标记所有可以访问的对象,这个阶段称之为标记阶段。阶段2:gc在整个堆中扫描并回收所有没有被标记的对象,这个阶段称之为清除阶段。
为了将活跃对象和垃圾对象给区分开来,我们在对象中记录了对象的状态,在每个Object 中添加一个字段marked,默认值为false. 当对象创建时候,所有的对象的marked 都是false,也就是未标记的。
标记算法的伪代码:
void mark (Object p)
if (!p.marked)
p.marked = true;
for each Object q referenced by p
mark (q);
当一个对象已经被标记过后,上述的递归算法就什么也不做,因而这也能保证递归能够顺利结束。 当所有可以访问的对象都被标记上了后,上述方法就会终止。
清除算法的伪代码如下:
void sweep ()
for each Object p in the heap
if (p.marked)
p.marked = false
else
heap.release (p);
在清除阶段,为了找出未被标记的对象,gc算法需要在整个heap中扫描所有的对象。在扫描过程中,那些未被标记的对象的空间就会被回收,同时在每个live对象中的marked也会被设置回false以便下次gc. 如下图所示:
图a显示了gc开始的条件,在此例子中,只有一个root变量。
图b显示了标记阶段的效果,在此阶段,所有活跃对象就已经被标记了。最后,
图C 显示了清除阶段完成留下的对象。在内存中,只有活跃对象保留了并且这个对象的marked字段都被重置为false.
标记清除算法的优点:
<!--[if !supportLists]-->1. <!--[endif]-->能够正确标记并回收垃圾,即使对于循环引用来说,也没有问题。
<!--[if !supportLists]-->2. <!--[endif]-->对引用变量的操作没有任何开销
标记清除算法的缺点:
1.导致应用程序挂起,对于那些有实时性约束的系统会带来一点问题。
标记清除算法引起的 碎片问题
碎片问题是在经历了好几次gc之后发生的现象。这个问题是在对中的对象容易变得比较分散,Live 的对象被一些小的没有分配的内存区域互相隔开来。 问题是,在某些情况下,即使实际上有足够的没有使用的内存,但是这些内存是不连续的, 由于对象通常占用连续的存储单元,,因此就变得有可能不能为一个对象分配空间.
标记清除算法不能标记出碎片,即使回收了所有垃圾对象的存储空间,堆可能会有太多的碎片而不能分配所需要的空间。