Java虚拟机的7中垃圾收集器---Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1

      如果说收集算法(标记-清除,复制,标记-整理、分代收集算法)是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。目前HotSpot虚拟机所包含的所有收集器如下图。

Java虚拟机的7中垃圾收集器---Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1_第1张图片

上面7种作用不同分代的收集器,如果两个收集器之间存在连线,就说明他们可以搭配使用。虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器。HotSpot实现如此多的垃圾收集器,就是因为目前没有完美的收集器出现,只能选择对应最合适的收集器。

一、理解相关概念

   1.并行(Parallel)

          并行指的是多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态

   2.并发(Concurrent)

         并行指的是用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行在另一个CPU上。

   3.吞吐量(Throughput)

           吞吐量就是CPU用于运行用户代码的时间CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+ 垃圾收集时间)

二、新生代收集器

    1.Serial收集器

       Serial收集器是最基本发展历史最悠久的收集器,它是采用复制算法新生代收集器。顾名思义Serial(串行)收集器是一个单线程收集器。它只会使用一个CPU或者一条收集线程去完成收集工作,在进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。("Stop The World")。曾经在JDK1.3.1之前是新生代收集的唯一选择。

Java虚拟机的7中垃圾收集器---Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1_第2张图片

       实际上Serial收集器并非是“老而无用,食之无味弃之可惜”的鸡肋,到目前为止,它依然是虚拟机运行在Client模式下默认的新生代收集器。它有着优于其他收集器的地方:简单而高效(与其他收集器的单线程相比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得更高的单线程收集效率。

    2.ParNew收集器

       ParNew收集器是Serial收集器的多线程版本,也是新生代收集器。除了使用多线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,在是线上,这两种收集器也共用了相当多代码。

Java虚拟机的7中垃圾收集器---Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1_第3张图片

        ParNew收集器除了多线程收集之外,其他与Serial收集器相比并没有太多创新之处。但它却是许多运行在Server模式下的虚拟机首选的新生代收集器。除了Serial收集器之外,目前只有它能和CMS收集器(Concurrent Mark Sweep)配合工作,CMS收集器是JDK1.5推出的具有划时代意义的收集器。它实现了让垃圾收集线程和用户线程(基本上)同时工作。

        ParNew收集器在单CPU环境下绝对不会比Serial收集器更过更好,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个CPU环境都不能百分百超越Serial收集器。在多CPU环境下,它对GC系统资源的有效利用是很有好处的。它默认开启的收集线程与CPU数量相同,在CPU非常多的情况下使用-XX:ParallerGCThreads参数进行设置。

     3.Parallel Scavenge收集器

        Parallel Scavenge收集器也是一个并行收集器,它也是使用复制算法的收集器Parallel Scavenge收集器的目标是达到一个可控制的吞吐量(Throughput)。吞吐量高则可以高效地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

       Parallel Scavenge收集器提供两个参数用于精准控制吞吐量,分别是最大垃圾停顿时间-XX:MaxGCPauseMillis参数和直接设置屯吞吐量大小的-XX:GCTimeRatio参数。

  • MaxGCPauseMillis参数允许的值是一个大于0的毫秒数,收集器能尽可能保证内存回收花费的时间不超过该值
  • GCTimeRatio参数的值应当是一个大于0且小于100的整数,也就是垃圾收集时间占总时间的比率,相当于吞吐量的倒数。默认值为99,即允许最大1%(1/(1+99))的垃圾收集时间。

    Parallel Scavenge收集器被称为“吞吐量优先”收集器。Parallel Scavenge收集器还有一个参数-XX:+UseAdaptiveSizePolicy。这是一个开关参数,打开之后就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。自适应调节机制策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。

Java虚拟机的7中垃圾收集器---Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1_第4张图片

       Parallel Scavenge收集器无法与CMS收集器配合使用,所以在JDK1.6推出Parallel Old之前,如果新生代选择Parallel Scavenge收集器,只能与老年代Serial Old收集器配合使用。

三、老年代收集器

   1.Serial Old收集器

       Serial Old是Serial收集器的老年代版本,他同样是单线程收集器,使用标记-整理算法。这个收集器主要意义是给Client模式下的虚拟机使用。如果在Server模式下,有两大用途;

  1. 在JDK1.5及以前版本中与Parallel Scavenge收集器搭配使用。
  2. 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。

  2.Parallel Old收集器

       Parallel Old收集器是Parallel Scavenge收集器老年代版本,使用多线程标记-整理算法。这个收集器在JDK1.6才提供,在此之前新生代的Parallel Scavenge收集器只能选择Serial Old收集器,别无选择(因为Parallel Scavenge收集器无法与CMS收集器配合使用)。所以就诞生了Parallel Old收集器。至此吞吐量优先收集器就有了比较名副其实的应用组合,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge收集器加上Parallel Old收集器。

Java虚拟机的7中垃圾收集器---Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1_第5张图片

     3.CMS收集器

       CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,从Mark Sweep可以看出CMS收集器基于标记-清除算法。它非常符合在互联网或者B/S系统的服务端上的Java应用,这些应用都非常重视服务的响应速度。

它的运作过程分为4个步骤:

  1. 初始标记(CMS initial mark):只是标记一个GC Roots能直接关联到的对象,速度很快。需要“Stop The World”。
  2. 并发标记(CMS concurrent mark):进行GC RootsTracing(可达性)的过程。在整个过程中耗时最长。
  3. 重新标记(CMS remark):为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这阶段的停顿时间一般比初始标记时间稍长,但远比并发标记时间短。需要“Stop The World”。
  4. 并发清除(CMS concurrent sweep):并发清除过程收集器线程和耗时最长的并发标记线程都可以与用户一起工作。

总体来说CMS收集器的内存回收过程是与用户线程一起执行的。

Java虚拟机的7中垃圾收集器---Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1_第6张图片

    CMS收集器的优点:并发收集低停顿。因此也被称为并发低停顿收集器(Concurrent Low Pause Collector)。

    CMS收集器的缺点:

  • 对CPU资源非常敏感。在并发阶段,它虽然不会导致用户线程停顿,但因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。CMS默认启动的回收线程是(CPU数量+3)/ 4 ,当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并且随着CPU数量的增加而下降。但是当CPU不足4个时,CMS对用户程序的影响可能变得很大。
  • 无法处理浮动垃圾(Floating Garbage),可能出现Concurrent Mode Failure失败而导致另一次Full GC的产生。由于CMS并发清理阶段用户线程还在运行,伴随程序运行自然就还会有新的垃圾不断产生。这部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理,这一部分垃圾称为浮动垃圾。由于垃圾收集阶段用户线程还需要运行,就需要预留足够的内存空间给用户线程使用,在JDK1.5默认设置下CMS收集器当老年代使用了68%的空间后就被激活,如果应用的老年代内存增长不是太快可以通过-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以便降低内存回收次数从而获取更好的性能,在JDK1.6中,CMS收集器的启动阈值已经提升至92%。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。
  • 标记-清除算法导致出现大量的空间碎片。空间碎片过多往往会出现老年代还有很大空间,但是无法找到足够大的连续空间分配大对象。不得不提前触发一次Full GC。CMS提供-XX:+UseCMSCompactAtFullCollection开关参数(默认开启),用于CMS收集器顶不住要进行Full GC时开启内存碎片的合并整理,但是过程无法并发,停顿时间不得不变长。

四、G1收集器

        G1(Garbage-First)收集器是当今收集器技术发展最前沿的成果之一。它是一款面向服务端应用的垃圾收集器,HotSpot开发团队赋予它的使命是(在比较长期的)未来可以替换掉JDK1.5中发布的CMS收集器。与其他收集器相比,G1有以下特点。

  • 并发与并行。G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短“Stop The World”停顿时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。
  • 分代收集。和其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个堆空间,但它能采用不同方式去处理新创建的对象和已存活一段时间、熬过多次GC的旧对象来获取更好的收集效果。
  • 空间整合。G1从整体看是基于标记-整理算法。从局部(两个Region之间)上来看是基于复制算法实现的。这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用空间,分配大对象时不会因为无法找到连续内存空间而提前触发一下次GC。
  • 可预测的停顿。这是G1相对CMS的一大优势,降低停顿时间是G1和CMS共同关注点,但是G1除了降低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在GC上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。

横跨整个堆内存

       在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。G1收集器将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,而都是一部分Region(不需要连续)的集合。

建立可预测的时间模型

        G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(也就是Garbage-First名称的由来)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获得尽可能高的收集效率。

避免全堆扫描

     G1把Java堆分为多个Region,就是“化整为零”。但是Region不可能是是孤立的,一个对象分配在某个Region中,它并非只能被本Region中其他对象引用,可以与整个Java堆任意对象发生引用关系。在做可达性判定对象是否存活的时候,需要扫描整个Java堆才能保证准确性。显示是对GC效率的极大伤害。

       为了避免全堆扫描,虚拟机为G1中的每个Region维护了一个与之对应的Remember Set虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中,如果是便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remember Set之中。当进行内存回收时,在GC根节点的枚举范围中加入Remember Set即可保证不对全堆扫描也不会有遗漏。

     如果不计算维护Remember Set的操作,G1收集器的运作大致可划分为以下几个步骤:

  • 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Nest Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短。
  • 并发标记(Concurrent Marking):从GC Roots开始对堆中对象进行可行性分析,找出存活的对象,这阶段耗时较长,但可以与用户线程并发执行。
  • 最终标记(Final Marking):为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remember Set Logs里面,最终标记阶段需要把Remember Set Logs的数据合并到Remember Set中,这阶段需要停顿线程,但是可并行执行。
  • 筛选回收(Live Data Counting and Evacuation):首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。

Java虚拟机的7中垃圾收集器---Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1_第7张图片

 

五、7中收集器对比

收集器 线程

串行/并

行/并发

新生代/老年代 算法 目标

Stop

The World

使用场景
Serial 单线程 串行 新生代 复制算法 响应速度优先 单CPU环境下的Client模式
ParNew 多线程 并行 新生代 复制算法 响应速度优先 不会 多CPU环境时在Server模式下与CMS配合使用

Parallel

Scavenge

多线程 并行 新生代 复制算法 吞吐量优先 不会 在后台预算不需要太多交互的任务
Serial Old 单线程 串行 老年代 标记-整理算法 响应速度优先 单CPU环境下的Client模式、CMS的后备预案
Parallel Old 多线程 并行 老年代 标记-整理算法 吞吐量优先 不会 在后台预算不需要太多交互的任务
CMS 多线程 并发 老年代 标记-清除算法 响应速度优先 集中在互联网网站或B/S系统服务端上的Java应用
G1 多线程 并发 新生代、老年代 分代收集算法
(复制算法+标记-整理算法)
响应速度优先 不会 面向服务端应用,将来替换CMS

你可能感兴趣的:(JVM)