yihau

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

1、哪些对象需要回收

  在进行垃圾回收的时候需要确定哪些对象活着,哪些对象死去(没有引用指向的对象)。先来看判断对象是否存活的方法。

1.1引用计数法

  给对象添加引用计数器,每当有引用到此对象,计数器+1,引用失效的时候计数器-1;但是,主流的JVM都没有用这种方法,这种方法的缺点是无法解决对象之间的循环引用问题。举例来说

public class ReferenceCountingGC{
  Object instance ;
  public static void main(String[] args){
       ReferenceCountingGC objA = new ReferenceCountingGC();
       ReferenceCountingGC objB = new ReferenceCountingGC();
       objA.instance = objB;
    objB.instance = objA;
    objA = null;
    objB = null;
    System.gc();    
 }  
}

这两个对象都已经赋值为null,也就是没有外部引用了,但是两个对象内部还是互相引用着对方,所以引用计数都不为0;但是这段代码是可以被虚拟机回收的,所以证明了JVM不是用的这种方法来判断对象是否存活。

1.2可达性分析算法

可达性分析算法是通过“GCroots”为起点,开始向下搜索,当通过GCRoots可以到达某个对象的时候,说明此对象存活,否则证明此对象不可用。这种算法可以有效的避免对象之间的循环引用问题。GCRoots是一组活跃的引用,而不是对象,包括

  • 所有Java线程当前活跃的栈帧里指向堆里对象的引用
  • VM的一些静态数据结构指向堆里对象的引用
  • JNI handlers
  • 方法区类中静态属性引用的对象
  • 方法区中常量引用的对象

2、垃圾收集算法

2.1、标记-清除算法

  Mark-Sweep算法分为标记和清除两个阶段,首先标记需要回收的对象,然后开始清除。缺点有两个:

  • 效率低
  • 会造成内存碎片,内存碎片对于大对象的分配影响很大,大对象需要一块比较大的连续内存区域,如果内存中很多这样的碎片的话会需要频繁GC。

2.2、复制算法

  复制算法是将内存区域分为两块,每次只使用一块,一块满了后就将存活的对象复制到另一块,顺便可以重新排列对象,消除碎片,然后将内存空间清理掉。这种算法的代价是内存只有原来的一半,损失太大。现代的JVM基本上是用这种算法来回收新生代区域。使用的时候将新生代分为Eden区和两块survivor区,比例默认为8:1:1,每次使用Eden区和一个survivor区,在GC的时候将存活的对象放到另一块survivor区,如果空间不够就放到老年代。

2.3、标记-整理算法

  标记整理是在标记清除算法的基础上,不直接清除,而是将存活对象像一端移动,整理内存碎片空间,然后将存活区域外的空间清理。这种算法一般用在老年代。

2.4、分代收集算法

  在新生代,对象存活时间短,使用复制算法,在老年代对象存活率高,使用标记-整理算法。

3、垃圾收集器

3.1、Serial收集器

  最基本的收集器,垃圾收集的时候要暂停所有的用户线程stop the world,而且只用一条线程去收集垃圾,收集完成之后再恢复用户线程。

3.2、ParNew收集器

  和Serial收集器一样,只不过在收集的时候使用多条线程同时收集。可以使用+XX:ParallelGCThreads来控制垃圾收集的线程数

3.3、Parallel Scavenge收集器

  Parallel Scavenge收集器关注点是吞吐量,而不是停顿时间。就是使垃圾收集的时间占程序运行时间的比例尽可能小,当然他也是多线程的收集器。

3.4、Serial Old收集器

  Serial Old收集器是Serial收集器的老年代版本,也是单线程的收集器,使用标记整理算法。

3.5、Parallel Old收集器

  Parallel Old是Parallel Scavenge的老年代版本,可以和Parallel Scavenge组合完成吞吐量优先的组合。

3.6、CMS收集器

  CMS是以停顿时间最短为目标的收集器,基于标记-清除算法。整个收集阶段分为4步

  • 初始标记,需要stop the world 需要停顿,但是只标记GCRoots的直接引用对象,很快。
  • 并发标记,进行所有对象的可达性分析,耗时长,但是这时标记工作可以和用户线程一起工作。
  • 重新标记,这一步只标记在并发标记过程中变动的对象,这部分时间也很短。
  • 并发清除,清除对象不需要停顿。

  CMS的优点是并发清除,停顿时间短,缺点是CMS对CPU敏感,在CPU核数少的时候性能明显下降,CMS在并发清除的时候程序还是会产生垃圾,所以CMS在GC的时机是老年代未填满的时候,还要保留一部分空间给这些垃圾,如果清除的时候产生垃圾过多,导致老年代空间不足,就会产生Concurrent Mode Failure。还有就是会产生内存碎片,因为使用标记-清除。

3.7、G1收集器

  G1收集器将java堆区域划分为多个region,而不是老年代和新生代(仍然存在逻辑上的老年代和新生代),G1收集器不需要和其他收集器组合可以自己处理新生代和老年代的垃圾收集,G1整体看是标记整理算法,局部(两个region之间)看是复制算法,所以G1不会产生内存碎片,G1还可以建立可预测的停顿时间模型。G1收集器分以下几步

  • 初始标记,
  • 并发标记,和CMS相同
  • 最终标记,修正并发标记期间产生变化的记录,需要停顿
  • 筛选回收,根据各个region的回收价值,和用户期望的GC停顿时间来决定回收哪些region。需要停顿。
    G1收集器通过给每个region维护一个rememberd Set 来记录引用此region的对象的引用,来避免垃圾收集的时候的全堆扫描。只需要扫描本region和对应的rememberd Set即可。
posted on 2018-03-09 22:27  yihau  阅读(113)  评论(0编辑  收藏  举报