JVM(五):GC垃圾收集器分类

1、Serial收集器新生代

单线程,在进行垃圾收集时必须暂停其他所有的工作线程(“Stop the World“)。虚拟机运行在Client模式下的默认新生代收集器。简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程效率。

2、ParNew收集器新生代

ParNew收集器其实是前面Serial的多线程版本,除使用多条线程进行GC外,包括Serial可用的所有控制参数、收集算法、STW、对象分配规则、回收策略等都与Serial完全一样(也是VM启用CMS收集器-XX: +UseConcMarkSweepGC的默认新生代收集器)。

由于存在线程切换的开销,ParNew在单CPU的环境中比不上Serial,且在通过超线程技术实现的两个CPU的环境中也不能100%保证能超越Serial。 但随着可用的CPU数量的增加,收集效率肯定也会大大增加(ParNew收集线程数与CPU的数量相同,因此在CPU数量过大的环境中,可用-XX:ParallelGCThreads参数控制GC线程数)。

3、Parallel Scavenge收集器(“吞吐量优先收集器)(新生代

        使用复制算法,并行多线程,这些特点与ParNew一样,它的独特之处是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目的则是达到一个可控制的吞吐量Throughput),即CPU用于运行用户代码的时间与CPU总消耗时间的比值,吞吐量=运行用户代码时间 /运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,吞吐量就是99%。

       停顿时间越短对于需要与用户交互的程序来说越好,良好的响应速度能提升用户的体验;

       高吞吐量可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适合在后台运算而不太需要太多交互的任务。

参数设置:

-XXMaxGCPauseMillis 控制最大垃圾收集停顿时间。(大于0的毫秒数)停顿时间缩短是以牺牲吞吐量和新生代空间换取的。(新生代调的小,吞吐量跟着小,垃圾收集时间就短,停顿就小)。

-XXGCTimeRatio 直接设置吞吐量大小,0<x<100 的整数,允许的最大GC时间=1/1+x)。

-XX+UseAdaptiveSizePolicy  一个开关参数,开启GC自适应调节策略(GC Ergonomics),将内存管理的调优任务(新生代大小-Xmn、Eden与Survivor区的比例-XX:SurvivorRatio、晋升老年代对象年龄-XX: PretenureSizeThreshold 、等细节参数)交给虚拟机完成。这是Parallel Scavenge收集器与ParNew收集器的一个重要区别,另一个是吞吐量。

4、Serial Old收集器老年代

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

Serial Old应用场景如下:

  • JDK 1。5之前与Parallel Scavenge收集器搭配使用;
  • 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时启用(见下:CMS收集器)。

5、Parallel Old收集器老年代

它是Parallel Scavenge收集器的老年代版本,多线程,使用“标记-整理“算法。在注重吞吐量及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old收集器。工作过程如下:

6、CMS收集器老年代

它是一种以获取最短回收停顿时间为目标的收集器。优点并发收集,低停顿。基于“标记-清除“算法。目前很大一部分Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验,CMS收集器就非常符合这类应用的需求。运作过程较复杂,分为4个步骤:

  • 初始标记(CMS initial mark):需要“Stop The World“,但初始标记仅标记一下GC Roots能直接关联到的对象,速度很快。
  • 并发标记(CMS concurrent mark):进行GC Roots Tracing 过程
  • 重新标记(CMS remark):需要“Stop The World“,修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。停顿时间:初始标记<重新标记<<并发标记
  • 并发清除(CMS concurrent sweep):时间较长。

由于CMS收集器将整个GC过程进行了更细粒度的划分,因此可以实现并发收集、低停顿的优势,但它也并非十分完美,其存在缺点及解决策略如下:

        1、CMS默认启动的回收线程数=(CPU数目+3)/4

当CPU数>4时,GC线程最多占用不超过25%的CPU资源,但是当CPU数<=4时,GC线程可能就会过多的占用用户CPU资源,从而导致应用程序变慢,总吞吐量降低。

        2、无法处理浮动垃圾可能出现Promotion Failure、Concurrent Mode Failure而导致另一次Full GC的产生。浮动垃圾是指在CMS并发清理阶段用户线程运行而产生的新垃圾。 由于在GC阶段用户线程还需运行,因此还需要预留足够的内存空间给用户线程使用,导致CMS不能像其他收集器那样等到老年代几乎填满了再进行收集。因此CMS提供了-XX:CMSInitiatingOccupancyFraction参数来设置GC的触发百分比,当老年代的使用空间超过该比例后CMS就会被触发(JDK 1.6之后默认92%)。 但当CMS运行期间预留的内存无法满足程序需要,就会出现上述Promotion Failure等失败,这时VM将启动后备预案: 临时启用Serial Old收集器来重新执行Full GC(CMS通常配合大内存使用,一旦大内存转入串行的Serial GC,那停顿的时间就是大家都不愿看到的了)。

      3、最后,由于CMS采用”标记-清除”算法实现,可能会产生大量内存碎片。 内存碎片过多可能会导致无法分配大对象而提前触发Full GC。 因此CMS提供了-XX:+UseCMSCompactAtFullCollection开关参数,用于在Full GC后再执行一个碎片整理过程。 但内存整理是无法并发的,内存碎片问题虽然没有了,但停顿时间也因此变长了,因此CMS还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction用于设置在执行N次不进行内存整理的Full GC后,跟着来一次带整理的(默认为0: 每次进入Full GC时都进行碎片整理)。

7、G1收集器Garbage First

G1(Garbage-First)是一款面向服务端应用的收集器,主要目标用于配备多颗CPU的服务器治理大内存。G1垃圾收集器被认为是GMS的长期可替代方案,JDK1.7之后的默认垃圾收集器。

- -XX:+UseG1GC 启用G1收集器。

与其他基于分代的收集器不同,G1将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。

每块区域既有可能属于O区、也有可能是Y区,因此不需要一次就对整个老年代/新生代回收。 而是当线程并发寻找可回收的对象时有些区块包含可回收的对象要比其他区块多很多 虽然在清理这些区块时G1仍然需要暂停应用线程但可以用相对较少的时间优先回收垃圾较多的Region(这也是G1命名的来源)。 这种方式保证了G1可以在有限的时间内获取尽可能高的收集效率。

新生代收集

G1的新生代收集跟ParNew类似: 存活的对象被转移到一个/多个Survivor Regions。 如果存活时间达到阀值,这部分对象就会被提升到老年代。

G1的新生代收集特点如下:

  • 一整块堆内存被分为多个Regions。
  • 存活对象被拷贝到新的Survivor区或老年代。
  • 年轻代内存由一组不连续的heap区组成,这种方法使得可以动态调整各代区域尺寸。
  • Young GCs会有STW事件,进行时所有应用程序线程都会被暂停。
  • 多线程并发GC。

老年代收集

G1老年代GC会执行以下阶段:

注: 以下有些阶段也是年轻代垃圾收集的一部分。

index Phase Description
(1) 初始标记 (Initial Mark: Stop the WorldEvent) 在G1中,该操作附着一次年轻代GC,以标记Survivor中有可能引用到老年代对象的Regions。
(2) 扫描根区域 (Root Region Scanning: 与应用程序并发执行) 扫描Survivor中能够引用到老年代的references。 但必须在Minor GC触发前执行完。
(3) 并发标记 (Concurrent Marking : 与应用程序并发执行) 在整个堆中查找存活对象,但该阶段可能会被Minor GC中断。
(4) 重新标记 (Remark : Stop the WorldEvent) 完成堆内存中存活对象的标记。 使用snapshot-at-the-beginningSATB起始快照)算法,比CMS所用算法要快得多(空Region直接被移除并回收,并计算所有区域的活跃度)。
(5) 清理 (Cleanup : Stop the World Event and Concurrent) 见下 5-1、5-2、5-3
  5-1 (Stop the world 在含有存活对象和完全空闲的区域上进行统计
  5-2 (Stop the world 擦除Remembered Sets。
  5-3 (Concurrent) 重置空regions并将他们返还给空闲列表(free list)
(*) Copying/Cleanup (Stop the WorldEvent) 选择”活跃度”最低的区域(这些区域可以最快的完成回收)。 拷贝/转移存活的对象到新的尚未使用的regions。 该阶段会被记录在gc-log内(只发生年轻代[GC pause (young)],与老年代一起执行则被记录为[GC Pause (mixed)]

详细步骤可参考 Oracle官方文档-The G1 Garbage Collector Step by Step

  • G1老年代GC特点如下:
    • 并发标记阶段(index 3)
      1. 在与应用程序并发执行的过程中会计算活跃度信息。
      2. 这些活跃度信息标识出哪些regions最适合在STW期间回收(which regions will be best to reclaim during an evacuation pause)。
      3. 不像CMS有清理阶段。
    • 重新标记阶段(index 4)
      1. 使用Snapshot-at-the-Beginning(SATB)算法比CMS快得多。
      2. 空region直接被回收。
    • 拷贝/清理阶段(Copying/Cleanup Phase)
      • 年轻代与老年代同时回收。
      • 老年代内存回收会基于他的活跃度信息。

补充: 关于Remembered Set

G1收集器中,Region之间的对象引用以及其他收集器中的新生代和老年代之间的对象引用都是使用Remembered Set来避免扫描全堆。 G1中每个Region都有一个与之对应的Remembered Set,VM发现程序对Reference类型数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region中(在分代例子中就是检查是否老年代中的对象引用了新生代的对象),如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set中。 当内存回收时,在GC根节点的枚举范围加入Remembered Set即可保证不对全局堆扫描也不会有遗漏。

猜你喜欢

转载自blog.csdn.net/aichuanwendang/article/details/84589824