java垃圾清除算法

来源:互联网 发布:幼儿园软件设施 编辑:程序博客网 时间:2024/05/22 05:08

再讲java的垃圾回收机制之前,先要明白以下两个问题:

a. 对象的循环引用

b.  怎样找到虚拟机中存活的对象

下面我们来看看这两个概念:

a. 对象的循环引用:即A中的B的对象指向B的引用,而B中的A的对象指向A的引用。

代码如下:

class A{

public B b;

}

class B{

public A a";

}

class C{

public static void main(String[] args){

B b = new B();

A a = new A();

b = a.b;

    a = b.a;

}

}

b.怎样找到虚拟机中存活的对象?

虚拟机会找到一个根节点(具体找根节点的依据我不是很清楚,想知道的自己看看),然后从栈区或者静态存储区的这个根节点开始遍历所有的对象(这里在虚拟机中是树形结构),如果此对象从根节点开始可以遍历到此对象的节点,那么可以说这个对象时存活的,如果从根节点开始便利不到次对象,呢么这个对象就不是存活着的。


1.使用引用计数器

在每个对象中都设置一个计数器,当有引用连接到此对象时,让该对象的引用计时器的数量加1,,当引用离开此作用的对象时,让该变量的引用计数器减1。当发现某个对象的计数器计数为0时,往往立即回收此变量的空间,此过程必须对所有对象进行遍历,开销当然非常大,并且有的经常使用的对象计数器一旦为0就需要被垃圾回收器立即回收。此方法有个缺陷,就是,如果对象之间循环引用的话,就会出现“对象应该被回收,但引用计数器却不为0”的情况发生。


2. 停止-复制算法

从他的名字就可以看出来此算法是要将运行的程序停下来,然后才进行垃圾回收。垃圾回收时,将堆中的对象全部遍历(上一篇文章说过,java垃圾回收器只能看到new出来的对象,所以对象都在堆中),找到堆中仍然存活的对象然后将它复制到另一个堆中(在此堆中一个接一个紧密排列)。复制完成后第一个堆中的所有没有复制对象都可以被垃圾回收器所回收。由于在新的堆中对象紧密排列,所以就可以有效的给新的对象直接分配空间。所以说,java在堆上分配空间的效率可以和其他语言在栈上分配空间的速度相媲美,正是由于java虚拟机的空间回收直接影响他的空间分配。

此方法的缺点就是需要两个堆,在两个堆之间来回复制为免浪费时间,而且在程序运行时停下来进行垃圾回收这显然不合理,还有就是,在复制到新的堆时,需要将所有指向他的引用全部修改,这也是一个耗时操作。

3. 标记-清扫算法

标记-清扫的方法本身很慢,但是当只有少量垃圾需要回收的时候就很快了。其原理是:两个过程:从根节点出发,遍历所有对象(存活对象),遍历到的对象都将对他们进行标记,这个过程不会回收任何对象。只有全部标记工作完成后猜对没有进行标记的对象进行清理。但是在清理过程中会出现很多碎片,导致堆得空间不连续,要屎堆得空间连续还需要对他进行重新整理。

4.标记-整理算法

标记整理算法是将2和3的优点结合在一起。也分为两个过程:从根节点出发,遍历所有对象,存活对象),遍历到的对象都将对他们进行标记,这个过程不会回收任何对象。第二阶段清除未标记的对象并且将存活的对象压缩在一起。这个压缩不同于停止-复制算法的压缩,此算法中的压缩只是将存活的对象压缩到这个堆中的一端,紧密存放,只需要一个堆,所以更加便于维护。

5. 分代算法

在这里,虚拟机中的内存分配以“块”为单位进行分配。每个块都有相应的年龄(即代数(generation count))来记录它是否还应该存活。分代算法将java的对象分为三代:老年代,新生代,持久代

老年代:经过很多次垃圾回收以后仍然存活的对象就会被放进老年代中。老年代中存放的都是一些生命周期较长的对象。

新生代:一般来说,新生代分为3个区,1个Eden区,2个Survivor区,大多数新产生的对象都放在Eden区,当Eden区满的时候,就会将还存活的对象复制到其中一个Survivor区,然后当这个Survivor区有满的时候,就会将对象复制到另外一个Survivor区(两个Survivor区必须要有一个空闲),当这个Survivor区满的时候,就会将存活的对象复制到老年代。

持久代:存放静态文件以及比较大的对象,比如java类,方法等。


至于什么时候触发虚拟机的Scavenge GC,什么时候触发Full GC,下次再讲!

0 0