java 垃圾回收步步深入03----垃圾收集算法

                               java 垃圾回收步步深入03----垃圾收集算法

1.常用垃圾回收机制

1)标记-清除收集器

  这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。

2)标记-压缩收集器

  有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。

3)复制收集器

这 种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,虚拟机生成的新对象则放在另一半空间中。垃圾回收器运行时,它把可到达对象复制到另一半 空间,没有被复制的的对象都是不可达对象,可以被回收。这种方法适用于短生存期的对象,持续复制长生存期的对象由于多次拷贝,导致效率降低。缺点是只有一 半的虚拟机空间得到使用。

4)增量收集器

增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。

5)分代收集器

这种收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。虚拟机生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。这样可以减少复制对象的时间。

6)并发收集器

并发收集器与应用程序同时运行。这些收集器在某点上(比如压缩时)一般都不得不停止其他操作以完成特定的任务,但是因为其他应用程序可进行其他的后台操作,所以中断其他处理的实际时间大大降低。

7)并行收集器

并行收集器使用某种传统的算法并使用多线程并行的执行它们的工作。在多CPU机器上使用多线程技术可以显著的提高java应用程序的可扩展性。

 8)自适应收集器

根据程序运行状况以及堆的使用状况,自动选一种合适的垃圾回收算法。这样可以不局限与一种垃圾回收算法。

2.垃圾回收算法

在Java中可以作为根的对象包括以下几种:

  1. 虚拟机栈中引用的对象;
  2. 方法区中的类静态属性引用的对象;
  3. 方法区中常量引用的对象;
  4. 本地方法栈中JNI的引用的对象;

那么,如何回收?

  其实对象并非只有“有用”和“没用”这两种状态,因为在GC之前还会有机会调用finalize方法,在改方法中对象可能复活(但是只能复活一次)。那么,指向对象的引用可能有以下四种:

  1. 强引用;
  2. 软引用;
  3. 弱引用;
  4. 虚引用;

a."标记-清除算法"

最简单的回收算法是“标记-清除算法”,其实就是标记出来垃圾然后将其释放:

从上图中也非常容易看到这种算法的缺点:容易产生内存碎片,另一个不太明显的缺点就是效率低。为什么效率低呢?因为堆上面大部分的对象是垃圾(至于为什么就不多说了),而在该算法中删除的过程重要处理的就是这部分,所以会慢一点。

b.“复制算法” 

复制算法”正好可以解决这个问题:

实现的时候将内存分成两部分,每次只是用其中的一部分,在进行垃圾回收的时候将正在使用的对象拷贝到另一半的内存中。但是,这样很明显会浪费很多的内存,那么怎么优化呢?还是从垃圾比较多的角度来想。每次回收之后的对象占用的空间其实很小的,也就是只要很少的内存,也就是:

较大的一块内存区域是Eden,还有两个较小的Survivor,具体的过程如上(两个Surivior交替使用)。当然Survivor的大小可能不够用,这个时候就需要“内存担保”了。那么现在再来看下垃圾回收的过程中还有没有做一些无用功?堆上的一些对象可能有很长的生命周期(在Web应用中更是如此),那么这些对象就会被不停地在两个Survivor之间拷来拷去。这样也就有了“分代收集算法”:

c.分代收集算法

  其实分代收集并没有什么特别的地方,如果一个对象的生命周期比较长,那么就把他放到老年代中。那怎么去预测一个对象的生命长短?一个很简单的方法就是如果活了足够久的时间,那么我们就认为这个对象会继续活很久(当然这个阀值可以去动态计算),当然也有可能刚进入老年代就变成垃圾了,不过这个概率应该很小。老年代是没有内存担保的(还有一个原因就是老年代中的存活率很高),所以适合用“标记-清理”的方式来收集。

上面的这些算法都有一个共同的问题:垃圾回收都是全量的。如果全量收集消耗了很多的时间,那么用户程序的体验就会下降很多(尤其是搜索引擎)。那么每次不是全量地,而是只回收一部分的垃圾,这样就能减少一次回收的时间(也就减少了用户程序的间隔),那么就有了“火车算法”:

 

首先将内存分成不同的块,回收也是针对块来操作的,所以检查的是有没有外部的引用指向块内的对象,主要的操作如下:

  1. 新的对象要不被打包成车厢挂到现有的火车尾部,或者当成一辆新的火车进站;
  2. 任何被其他车厢引用的对象都移出去;
    1. 如果存在来自非成熟对象空间的引用,将其移到其他的火车上去;
    2. 如果被其他火车引用,移动到对应的火车上去;
    3. 1、2完成之后,将被本列火车引用的对象移动到最后一节车厢去;
  3. 当然,可能编号最小的车厢整个就被回收了;

 3. 火车算法

            垃圾收集算法一个很大的缺点就是难以控制垃圾回收所占用的CPU时间,以及何时需要进行垃圾回收。火车算法是分代收集器所用的算法,目的是在成熟对象空间中提供限定时间的渐进收集。目前应用于SUN公司的Hotspot虚拟机上。

              在火车算法中,内存被分为块,多个块组成一个集合。为了形象化,一节车厢代表一个块,一列火车代表一个集合,见图一

图一

注意每个车厢大小相等,但每个火车包含的车厢数不一定相等。垃圾收集以车厢为单位,收集顺序依次为1.11.21.31.42.12.22.33.13.23.3。这个顺序也是块被创建的先后顺序。

垃圾收集器先从块1.1开始扫描直到1.4,如果火车1四个块中的所有对象没有被火车2和火车3的对象引用,而只有火车1内部的对象相互引用,则整个火车1都是垃圾,可以被回收。

图二,车厢1.1中有对象A和对象B1.3中有对象C1.4中有对象D车厢2.2中有对象E,车厢3.3中有对象F。在火车1中,对象C引用对象A,对象B引用对象D,可见,火车2和火车3没有引用火车1的对象,则整个火车1都是垃圾。

图二

如果火车1中有对象被其它火车引用,见图三,扫描车厢1.1时发现对象A被火车2中的E引用,则将对象A从车厢1.1转移到车厢2.2,然后扫描A引用的对象D,把D也转移到车厢2.2,然后扫描D,看D是否引用其它对象,如果引用了其它对象则也要转移,依次类推。扫描完火车1的所有对象后,剩下的没有转移的对象都是垃圾,可以把整个火车1都作为垃圾回收。注意如果在转移时,如果车厢2.2空间满了,则要在火车2末尾开辟新的车厢2.4,将新转移的对象都放到2.4,即火车的尾部)

图三

     补充说明:垃圾回收器一次只扫描一个车厢。图三中的对象B与C并不是立即被回收,而是先会被转移到火车1的尾部车厢。即扫描完1.1后,B被转移到火车1 尾部,扫描完1.3后,C被转移到车尾。等垃圾收集器扫描到火车1尾部时,如果仍然没有外部对象引用它们,则B和C会被收集。

    火 车算法最大的好处是它可以保证大的循环结构可以被完全收集,因为成为垃圾的循环结构中的对象,无论多大,都会被移入同一列火车,最终一起被收集。还有一个 好处是这种算法在大多数情况下可以保证一次垃圾收集所耗时间在一定限度之内,因为一次垃圾回收只收集一个车厢,而车厢的大小是有限度的。

发布了82 篇原创文章 · 获赞 76 · 访问量 23万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章