JVM垃圾收集器总结

Serial收集器

  • 收集年代:新生代
  • 线程:单线程
  • 收集算法:复制算法
  • 使用场景:在client模式下的默认新生代收集器。
  • 特点:在进行垃圾收集时,必须暂停其他所有的工作线程,直到serial收集器收集结束。
  • 优点:简单高效。对于限定单个CPU的环境来说,serial收集器由于没有线程交互的开销,可以获得最高的单线程手机效率。

stop the world

虚拟机在后台自动发起和自动完成的一项工作,在用户不可见的情况下把用户所有的工作线程全部停掉。

ParNew收集器

  • 收集年代:新生代
  • 线程:多线程
  • 收集算法:复制算法
  • 使用场景:是许多运行在server模式下的虚拟机首选的新生代收集器(除了serial收集器外,只有其能与CMS收集器配合工作)

Parallel Scavenge收集器

  • 收集年代:新生代
  • 线程:多线程
  • 收集算法:复制算法
  • 特点:
      - Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量(-XX:MaxGCPauseMillis:控制最大垃圾收集器停顿时间,-XX:GCTimeRatio:直接设置吞吐量)。
      - Parallel Scavenge收集器可以配合自适应调节策略使用。其提供了一个参数-XX:+UseAdapiveSizePolicy,开启该参数,则虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整-Xmn:新生代的大小,-XX:SurvivorRatio:Eden区和Survivor区的比例,-XX:PretenureSizeThreshold:晋升老年代对象大小等参数以提供最合适的停顿时间或者最大吞吐量,这种调节方式被称为GC自适应调节策略

Serial Old收集器

  • 收集年代:老年代
  • 线程:单线程
  • 收集算法:标记整理算法
  • 使用场景:给在client模式下的虚拟机使用
  • 用途:
      1. 在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用。
      2. 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。

Parallel Old收集器

  • 收集年代:老年代
  • 线程:多线程
  • 收集算法:标记整理算法
  • 起始版本:JDK1.6开始提供
  • 特点:可以与Parallel Scavenge收集器搭配使用

CMS收集器

  • 收集年代:老年代
  • 线程:多线程
  • 收集算法:标记清除算法
  • 特点:以获取最短回收停顿时间为目标的垃圾收集器
  • 收集步骤:
    • 初始标记:仅仅只标记一下GC Roots能直接关联的对象(速度很快)。
    • 并发标记:进行GC Roots Tracing的过程。
    • 重新标记:为了修正并发标记期间用户程序继续运行而导致标记产生变动的那一部分对象的标记记录(这一阶段的停顿时间一般比初始标记阶段的稍长一些,但远比并发标记的时间短)。
    • 并发清除:进行真正的清除。
  • 注意:初始标记和重新标记这两个步骤仍需要进行stop the world
  • 缺点:
    • 对CPU资源敏感。

    • 无法处理浮动垃圾,可能会出现Concurrent Mode Failure失败导致另一次Full GC的产生,当CMS在运行期间预留的内存无法满足程序需要,就会出现一次Concurrent Mode Failure失败,那么虚拟机就会启动备用方案:使用Serial Old收集器来重新进行老年代的垃圾收集,这样就会导致停顿时间变得很长。

      -XX:CMSInitiatingOccupancyFraction:设置CMS收集器当老年代的内存使用达到一定百分比的时候会触发垃圾回收,把该参数设置得太高会导致大量得Concurrent Mode Failure,导致性能下降。

    • 由于CMS收集器采用标记清除算法,因此收集后会产生大量空间碎片。

      为了解决此问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection参数,用于解决CMS收集器在顶不住要进行Full GC时开启内存碎片的合并整理过程,内存整理的过程是无法并发的,所以停顿时间不得不变长。并且CMS收集器还提供了另一个参数-XX:CMSFullGCsBeforeCompaction,用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的(默认值为0,表示每次进入Full GC时都进行碎片整理)。

浮动垃圾

由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就称为浮动垃圾

G1收集器

  • 收集年代:整体上看不再以新生代和老年代来划分堆,而是把堆划分成多个大小相等的独立区域(Region),并且在Region内还保留着新生代和老年代的概念。

  • 收集算法:从整体来看G1收集器是基于标记整理算法的,从局部上(Region)来看是基于复制算法实现的。

  • 特点:

    • 可预测的停顿:G1收集器处了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾回收上的时间不超过N毫秒。

      可预测停顿的实现原理:

      G1收集器之所以能建立可预测的停顿时间模型,是因为它在Java堆中进行全区域的垃圾收集。G1收集器跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。

  • G1收集器如何确保在进行可达性分析时不进行全堆的扫描?

    • 背景:G1收集器是把Java堆划分成多个Region,但是垃圾收集并不能以Region为单位进行回收的,因为每个Region都不是独立的,一个对象分配在某个Region中,那么它并非只能被本Region中的对象引用,而是可以与整个堆中的任意对象发生引用关系。

    • 问题:如果在可达性分析对象是否存活时,G1收集器是否需要扫描整个堆?

    • 解决:在G1收集器中,Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象,虚拟机都是使用Remembered Set来避免全堆扫描的。‘

Remembered Set

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

  • 收集步骤:
    • 初始标记:标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段的用户程序并发运行时,能在正确可用的Region中创建新的对象,这阶段需要停顿线程,但是耗时很短。
    • 并发标记:从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但是可与用户程序并发执行。
    • 最终标记:为了修正并发标记期间用户程序继续运作而导致标记产生变动的那一部分的标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行。
    • 筛选回收:首先对各个Region的回收价值和成本进行排序,根据用户所期望的用户的GC停顿时间来指定回收计划(这个阶段也是可以和用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将会大幅提高收集效率)。
posted @ 2021-04-14 16:13  yghr  阅读(68)  评论(0)    收藏  举报