HotSpot虚拟机中的7种垃圾回收器

垃圾收集器概述

垃圾收集器组合

  • 图中展示了7种垃圾收集器,Serial、ParNew、Parallel Scavenge、CMS、Serial old、Parallel Old、G1
  • 带有连线的收集器代表可以组合使用
  • Serial Old作为CMS出现"Concurrent Mode Failure"失败的后备预案

并发垃圾收集和并行垃圾收集的区别

  • 并行指多条垃圾收集线程并行执行,但是用户线程需要等待,即进入stop the world状态,对应的垃圾收集器有ParNew、Parallel Old、Parallel Scavenge。
  • 并发指用户线程和垃圾收集线程同时执行,不一定是同时执行,可能是交替执行,对应的垃圾收集器有CMS、G1。

Minor GC和Full GC

Minor GC表示新生代垃圾收集,Full GC表示新生代和老年代垃圾收集,Major GC表示老年代垃圾收集。

Serial收集器

  1. 特点
  • 针对新生代、采用复制算法、单线程收集
  • 进行垃圾收集是会暂停所有的工作线程
    Serial/Serial Old组合收集器运行示意图如下:
  1. 应用场景
  • HotSpot在client模式下默认的新生代垃圾收集器
  • 对于限定单个CPU的环境来说,Serial收集器没有线程交互(切换)开销,可以获得最高的单线程收集效率
  1. 设置参数
  • “-XX:+UseSerialGC”:添加该参数来显式的使用串行垃圾收集器。

ParNew收集器

  1. 特点
  • 针对新生代、采用复制算法、多线程收集
  • 进行垃圾收集时会停止所有的用户线程
    ParNew/Serial Old组合收集器运行示意图如下:
  1. 应用场景
  • 在Server模式下,ParNew收集器是一个非常重要的新生代收集器,因为除Serial外,目前只有它能与CMS收集器配合工作。
  • 但在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。
  1. 设置参数
  • “-XX:+UseConcMarkSweepGC”:指定使用CMS后,会默认使用ParNew作为新生代收集器
  • “-XX:+UseParNewGC”:强制指定使用ParNew
  • “-XX:ParallelGCThreads”:指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同
  1. 为什么只有ParNew能与CMS收集器配合?
  • CMS是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作;CMS作为老年代收集器,但却无法与JDK1.4已经存在的新生代收集器Parallel Scavenge配合工作;因为Parallel Scavenge(以及G1)都没有使用传统的GC收集器代码框架,而另外独立实现;而其余几种收集器则共用了部分的框架代码;

Parallel Scavenge收集器

  1. 特点
  • 针对新生代,采用复制算法,多线程收集
  • CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达一个可控制的吞吐量(Throughput)
  1. 应用场景
  • 以高吞吐量为目标,即减少垃圾收集时间,让用户代码获得更长的运行时间(减少垃圾收集时间并不意味着减少用户线程停顿时间)
  • 当应用程序运行在具有多个CPU上,对暂停时间没有特别高的要求时,即程序主要在后台进行计算,而不需要与用户进行太多交互
  1. 设置参数
  • “-XX:MaxGCPauseMillis”:控制最大垃圾收集停顿时间, MaxGCPauseMillis设置得稍小,停顿时间可能会缩短,但也可能会使得吞吐量下降,因为可能导致垃圾收集发生得更频繁。
  • “-XX:GCTimeRatio”:设置垃圾收集时间占总时间的比率,0
  • “-XX:+UseAdptiveSizePolicy”: 开启这个参数后,就不用手工指定一些细节参数,如新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等,JVM会根据当前系统运行情况收集性能监控信息,动态调整这些参数,以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomiscs)。
  1. 吞吐量
  • 吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)

Serial Old收集器

  1. 特点
  • 针对老年代,采用标记整理算法,单线程收集
    Serial/Serial Old收集器运行示意图如下:
  1. 应用场景
  • 主要用于Client模式
  • 而在Server模式有两大用途:(1)在JDK1.5及之前,与Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配)(2)作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用

Parallel Old收集器

  1. 特点
  • 针对老年代,采用标记整理算法,多线程收集
    Parallel Scavenge/Parallel Old收集器运行示意图如下:
  1. 应用场景
  • JDK1.6及之后用来代替老年代的Serial Old收集器,特别是在Server模式,多CPU的情况下。
  1. 设置参数
  • “-XX:+UseParallelOldGC”:指定使用Parallel Old收集器

CMS收集器

并发标记清理(Concurrent Mark Sweep,CMS)收集器也称为并发低停顿收集器(Concurrent Low Pause Collector)或低延迟(low-latency)垃圾收集器。

  1. 特点
  • 针对老年代,采用标记清除算法,以获取最短停顿时间为目标,并发收集,需要更多内存。
  1. 应用场景
  • 与用户交互较多的场景,希望系统停顿时间最短,注重服务的响应速度, 如常见WEB、B/S系统的服务器上的应用。
  1. 设置参数
  • “-XX:+UseConcMarkSweepGC”:指定使用CMS收集器
  1. CMS收集器运作过程[2]
  • Phase1 :Initial Mark【初始标记】
    这个是CMS两次stop-the-world事件的其中一次,这个阶段的目标是:标记那些直接被GC root引用或被年轻代存活对象所引用的所有对象。如图:
    HotSpot虚拟机中的7种垃圾回收器_第1张图片
    上面有些对象是直接被GC ROOTS所引用的,有些对象是被年轻代引用的,都会被标记出来。
  • Phase2 : Concurrent Mark 【并发标记】
    在这个阶段Garbage Collector会遍历老年代,然后标记所有存活的对象,它会根据上个阶段找到GC ROOTS遍历查找。并发标记阶段,它会与用户的应用程序并发运行。并不是老年代所有的存活对象都会被标记,因为在标记期间用户的程序可能会改变一些引用。如下图:
    HotSpot虚拟机中的7种垃圾回收器_第2张图片
    在上面的图中,与阶段1的图进行对比,就会发现有一个对象的引用已经发生了变化,如标黑的那个对象。
  • Phase3 : Concurrent Preclean【并发预先清除】
    这也是一个并发阶段,与应用的线程并发运行,并不会stop应用的线程。在并发运行的过程中,一些对象的引用可能会发生变化,但是这种情况发生时,JVM会将包含这个对象的区域(Card)标记为Dirty,这也就是Card Marking。在pre-clean阶段,那些能够从Dirty对象到达的对象也会被标记,这个标记做完之后,dirty card标记就会被清除了。下面看下示意图:
    HotSpot虚拟机中的7种垃圾回收器_第3张图片
    上图中标红的则为Dirty,而能够被它所直接到达的对象也会被标记,标记完了则dirty card标记被清除,如下:
    HotSpot虚拟机中的7种垃圾回收器_第4张图片
  • Phase4 : Concurrent Abortable Preclean【并发可能失败的预先清除】
    这也是一个并发阶段,但是同样不会影响用户的应用线程,这个阶段是为了尽量承担STW(stop-the-world)中最终标记阶段的工作。这个阶段持续时间依赖于很多的因素,由于这个阶段是在重复做很多相同的工作,直接满足一些条件(比如:重复迭代的次数、完成的工作量或者时钟时间等)
  • Phase5 : Final Remark【最终重新标记】
    这是第二个STW阶段,也是CMS中的最后一个,这个阶段的目标是标记老年代所有的存活对象,由于之前的阶段是并发执行的,GC线程可能跟不上应用程序的变化,为了完成标记老年代所有存活对象的目标,STW就非常有必要了。通常CMS的Final Remark阶段会在年代代尽可能干净的时候运行,目的是为了减少连续STW发生的可能性(年轻代存活对象过多的话,也会导致老年代涉及的存活对象会很多)。这个阶段会比前面的几个阶段更复杂一些。
  • Phase6 : Concurrent Sweep【并发清除】
    这里不需要STW,它是与用户的应用程序并发运行,这个阶段是:清除那些不再使用的对象,回收它们的占用空间为将来使用,如图:
    HotSpot虚拟机中的7种垃圾回收器_第5张图片
  • Phase7 : Concurrent Reset【并发重置】
    这个阶段也是并发执行的,它会重设CMS内部的数据结构,为下次的GC做准备。
  1. CMS收集器的缺点
  • 对CPU资源非常敏感。并发收集虽然不会暂停用户线程,但因为占用一部分CPU资源,还是会导致应用程序变慢,总吞吐量降低,CMS的默认收集线程数量是=(CPU数量+3)/4。
  • 无法处理浮动垃圾,可能出现"Concurrent Mode Failure"失败。在并发清除时,用户线程新产生的垃圾,称为浮动垃圾, 这使得并发清除时需要预留一定的内存空间,不能像其他收集器在老年代几乎填满再进行收集,也可以认为CMS所需要的空间比其他垃圾收集器大。如果CMS预留内存空间无法满足程序需要,就会出现一次"Concurrent Mode Failure"失败,这时JVM启用后备预案:临时启用Serail Old收集器,而导致另一次Full GC的产生。
  • 产生大量内存碎片。由于CMS基于"标记-清除"算法,清除后不进行压缩操作,产生大量不连续的内存碎片会导致分配大内存对象时,无法找到足够的连续内存,从而需要提前触发另一次Full GC动作。由于空间不再连续,CMS需要使用可用"空闲列表"内存分配方式,这比简单实用"碰撞指针"分配内存消耗大。

G1收集器

G1将堆分割成一组等大小的堆区,一个区是内存分配和回收的基本单元.在任何一个给定的时间,每一个区可以是空的,也可以分配了特殊的代(年轻或老年).当有内存分配请求到来时,内存管理者上交空闲的分区,把它们指派给一个代并且交给应用程序,作为应用程序可自由分配的自由空间.大体如下图所示:
HotSpot虚拟机中的7种垃圾回收器_第6张图片
上图中,纯红色为eden区,标有’S’的为幸存者区,与此前其他的垃圾收集器功能保持一致,不同之处在于G1中这些同代的分区自身并不连续.老年代分区由浅蓝色表示,它的占用者可能会是跨多个分区的大型对象(如带有’H’的)。

  1. 特点
  • 并行与并发,能充分利用多CPU、多核环境下的硬件优势,可以并行来缩短"Stop The World"停顿时间,也可以并发让垃圾收集与用户程序同时进行。
  • 分代收集,收集范围包括新生代和老年代 ,能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配,能够采用不同方式处理不同时期的对象。将整个堆划分为多个大小相等的独立区域(Region),新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合。
  • 结合多种垃圾收集算法,空间整合,不产生碎片,从整体看,是基于标记-整理算法,从局部(两个Region间)看,是基于复制算法,都不会产生内存碎片,有利于长时间运行。
  • 可预测的停顿:低停顿的同时实现高吞吐量,G1除了追求低停顿外,还能建立可预测的停顿时间模型,可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒。
  1. 应用场景
  • 面向服务端应用,针对具有大内存、多处理器的机器
  • 最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案。
  1. 设置参数
  • “-XX:+UseG1GC”:指定使用G1收集器
  • “-XX:InitiatingHeapOccupancyPercent”:当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45
  • “-XX:MaxGCPauseMillis”:为G1设置暂停时间目标,默认值为200毫秒
  • “-XX:G1HeapRegionSize”:设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048个Region
  1. 为什么G1收集器可以实现可预测的停顿?
    G1可以建立可预测的停顿时间模型,是因为:可以有计划地避免在Java堆的进行全区域的垃圾收集,G1跟踪各个Region获得其收集价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region,这就保证了在有限的时间内可以获取尽可能高的收集效率。
  2. 一个对象被不同区域引用的问题
    一个Region不可能是孤立的,一个Region中的对象可能被其他任意Region中对象引用,判断对象存活时,是否需要扫描整个Java堆才能保证准确?在其他的分代收集器,也存在这样的问题。
    解决方法:
    无论G1还是其他分代收集器,JVM都是使用Remembered Set来避免全局扫描。每个Region都有一个对应的Remembered Set,每次Reference类型数据写操作时,都会产生一个Write Barrier暂时中断操作,然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region (其他收集器:检查老年代对象是否引用了新生代对象)如果不同,通过CardTable(CardTable是Remembered Set的一种实现)把相关引用信息记录到引用指向对象的所在Region对应的Remembered Set中。当进行垃圾收集时,在GC根节点的枚举范围加入Remembered Set,就可以保证不进行全局扫描,也不会有遗漏。
  3. G1收集器运作过程
    (1)初始标记(Initial Marking)。仅标记一下GC Roots能直接关联到的对象,且修改TAMS(Next Top at Mark Start),让下一阶段并发运行时,用户程序能在正确可用的Region中创建新对象,需要"Stop The World",但速度很快。
    (2)并发标记(Concurrent Marking)。 进行GC Roots Tracing的过程,刚才产生的集合中标记出存活对象,耗时较长,但应用程序也在运行,并不能保证可以标记出所有的存活对象。
    (3)最终标记(Final Marking)。为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录,上一阶段对象的变化记录在线程的Remembered Set Log,这里把Remembered Set Log合并到Remembered Set中。需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短,采用多线程并行执行来提升效率。
    (4)筛选回收(Live Data Counting and Evacuation)。首先排序各个Region的回收价值和成本,然后根据用户期望的GC停顿时间来制定回收计划,最后按计划回收一些价值高的Region中垃圾对象。回收时采用"复制"算法,从一个或多个Region复制存活对象到堆上的另一个空的Region,并且在此过程中压缩和释放内存,可以并发进行,降低停顿时间,并增加吞吐量。
    G1收集器运行示意图如下:

[1]https://www.cnblogs.com/cxxjohnson/p/8625713.html
[2]https://www.cnblogs.com/webor2006/p/11110263.html

你可能感兴趣的:(HotSpot虚拟机中的7种垃圾回收器)