GC:深入理解GC--细数GC回收器

GC:深入理解GC--细数GC回收器

    前言:本文将深入解释GC的运行原理--什么对象会被GC回收;以及GC回收器的区别

    GCROOT

    大家都知道,GC会对不可达对象进行内存回收,那么如何标记不可达对象呢?

    这里引出一个GCROOT的概念,一个对象如果和GCROOT对象没有链接,就说明这个对象不可达,便可以被回收。

    通常来说,以下的对象会被作为GCROOT对象:

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

    垃圾回收算法

    垃圾回收算法也就是GC对垃圾对象回收的方式,在上一文,GC对堆逻辑分代的博客中,已经描述过关于复制算法、标记清理算法的内容,此处我们再做复习:

    1.复制算法

    复制算法应用在新生代中,新生代被分为Eden区(新生对象待的地方,如果对象过大,会被直接放入老年代),SurvivorFrom区域,SurvivovrTo区域。

    在一次MinorGC后,Eden区域和SurvivorFrom区域的存活的对象会被放入SurvivorTo区域中,并且年龄加1,而后,Eden区域和SurvivorFrom区域中的对象就会被删除。

    而风水轮流转,下一次MinorGC时,SurvivorTo区域中的对象就会被放入SurvivorFrom区域中,接受MinorGC。而当对象的年龄到达一个临界值的时候,对象也会被放入老年代。

    研究表明,百分之98的对象都是朝生夕死的,所以,没有必要用一比一的比例划分Eden区域和Survivor区域,因为“死了”的对象都被回收了,存活的对象理论上才占百分之二,那么多余的Survivor区域就没啥用了。所以,Eden区域和Survivor区域的比例,默认是8:1,这个数值可以进行修改。

    2.标记-清理算法

    先说说标记:我们在文章的开头说了有关GCROOT的概念,标记则会标记所有GCROOT链接下的对象。

    而清理,就会将未被标记的对象给清理掉。

    标记清理算法存在一个很致命的缺陷,清理后的内存区域,大多不是连续的,若是此时一个内存较大的对象进入老年代,很容易无法分配内存,从而触发FullGC。

    3.标记-整理算法

    有问题自然就要解决问题,标记-整理算法的就可以解决以上的内存不连续的问题。

    标记-整理算法,不仅仅只做标记,还会将标记的存活对象挪到内存的一端,之后再将其他的垃圾对象清除,如此一来,就解决了标记-清理算法带来的垃圾碎片问题。

    以上就是GC的主要算法了,其实硬是要细分,GC还存在有各种各样的算法:诸如“分代算法”、“增量算法”等等,但核心的就是以上的三种算法。好的,接下来介绍本文的主角--垃圾回收器。

    垃圾回收器

    垃圾回收器准确的来说是JVM提供的不同GC机制,我们依次介绍。

    1.Serial垃圾回收器

    中文名是串行回收器,细分有两个回收器,分别提供给新生代和老年代。

    在新生代:使用复制算法进行垃圾回收;在老年代:使用标记-整理算法进行垃圾回收。

    其余的,两回收器便没有什么差异了,都是为单线程环境而打造的回收器,在垃圾回收时,开启单个线程进行垃圾回收,并且暂停所有的用户线程(Stop The World),这是JVMClient模式下的默认GC回收器。

    通过-UseSerialGC参数,可以使用两个新老代串行回收器的组合。

    在单CPU的环境下,由于没有线程交互的开销,效率会比较高,回收模型如下:

    

    2.ParNew垃圾回收器

    ParNew垃圾回收器,几乎是Serial回收器的多线程版,其余特性无差别。(注:ParNew垃圾回收器只有新生代回收器,不像Serial回收器,还有一个SerialOld回收器)

    在JVMServer模式下,ParNew垃圾回收器是新生代回收器的首选,很大部分原因,是因为在选择CMS垃圾回收器时,会默认使用ParNew垃圾回收器作为新生代回收器,即,使用参数-XX:+UseConcMarkSweepGC,便会默认使用ParNew垃圾回收器。

    同样的,也可以使用-XX:+UseParNewGC参数强制使用ParNew回收器,而通过-XX:ParallelGCThreads参数可以限制ParNew回收器的线程数量,ParNew回收器的模型如下图:


    3.Parallel Scavenge垃圾回收器

    英文翻译为并行清理,听上去和ParNew垃圾回收器没有任何区别,但是此回收器相对于ParNew来说,他关注吞吐量。

   所谓的吞吐量,就是用户线程执行的时间占比,当使用Parallel Scavenge垃圾回收器的时候,可以通过设置参数,-XX:MaxGCPauseMillis控制最大垃圾收集停顿时间和-XX:GCTimeRatio设置吞吐量大小。

    控制垃圾收集停顿时间,相应的就会牺牲吞吐量,因为垃圾回收的停顿时间减少,相应的垃圾回收频率就会变高,随之吞吐量自然下降。

    在JDK1.6以前,Parallel Scavenge只能配合使用SerialOld垃圾回收器,所以老年代无法控制吞吐量,在JDK1.6以后,Parallel Old实现了老年代版本的并行垃圾回收器,使用标记-整理算法对垃圾进行回收,其他与Parallel无区别,可以搭配Parallel使用。

    4.CMS垃圾回收器(Concurrent Mark Sweep)

    这是垃圾回收器中唯一一个使用标记-清理算法的垃圾回收器。

    由于并发的原因,他的垃圾回收停顿时间会比较小,但是相应的牺牲了吞吐量,停顿时间短的好处是能提高用户体验,所以,他被广泛地用于B/S架构中。

    CMS垃圾回收器回收垃圾分为四步:

  1. 初始标记:快速标记GCROOT能够关联到的对象,这个步骤会STW;
  2. 并发标记;
  3. 重新标记:此过程会STW,为了修正并发标记时,用户线程对标记的变化;
  4. 并发清理:清理过程是并发的;

    虽然说CMS垃圾回收器能够减少回收垃圾停顿时间,但是他的缺点也比较明显:

  1.  由于频繁的进行垃圾回收,所以,吞吐量会下降;
  2. 标记-清理带来的缺点:存在内存碎片;

    5.G1垃圾回收器(Garbage First)

    G1垃圾回收器在CMS垃圾回收器的基础上有所改进,比较明显的有两点:

    一是使用了标记-整理算法,解决了垃圾碎片问题;

    第二,G1垃圾回收器将回收内存分为了多个区域,会观察多个区域中垃圾的情况,并优先收集垃圾较多的区域,这也是他名字Garbage First的由来。

    第三,G1垃圾回收器不需要其他垃圾回收器的配合就可以管理两个分代。


    总结

    垃圾回收器,其实粗分有四种垃圾回收器,串行,并行,并发,G1。但是细分可以有七种,其中,串行回收器有两个垃圾回收器,分别在新老年代,是单线程的实现,而ParNew则是串行回收器新生代的多线程版,这前三者又有一个共同点,都是简单的实现垃圾回收,STW,并没有多余的参数关注吞吐量。

    于是关注吞吐量的并行垃圾回收器就出现了,它可以通过设置参数来改变吞吐量,和最大停顿时间,同样也有新老年代两种实现。

    CMS则是唯一的一个使用标记-清除算法的垃圾回收器,由于并发执行,CMS能够做到垃圾回收停顿时间的优化,但是相对牺牲了吞吐量,同时也造成了回收碎片,只有老年代的实现。最后就是G1回收器,G1回收器在CMS的基础上又做了优化,将内存分区域,挑选垃圾最多的区域进行回收,同时的,它使用标记-整理算法,也就解决了内存碎片的问题,此回收器无需其他回收器的配合就能完成新老代的回收。

猜你喜欢

转载自blog.csdn.net/that_is_cool/article/details/80859321
今日推荐