Java虚拟机的垃圾收集器

JVM

前言

垃圾回收器是垃圾回收的具体实现,并没有所谓的最好的垃圾回收器,只有最合适的垃圾回收器,在你实际应用中根据自己应用的场景选择合适的垃圾回收器。

垃圾收集器

  • Serial收集器
  • ParNew收集器
  • Parallel Scavenge收集器
  • Serial Old收集器
  • Parallel Old收集器
  • CMS收集器
  • G1收集器

注:以上列出的垃圾收集器是基于JDK 1.7 Update 14之后的HotSpot虚拟机。

Serial收集器

工作特点:

采用复制算法的新生代收集器,是一个单线程的垃圾收集器,他在工作的时候只是用一个CPU或者一条线程去完成垃圾收集工作,而且他在进行垃圾收集的时候必须暂停所有的工作线程直至垃圾回收结束。也就是说在进行垃圾收集的时候,所有的工作线程都会被停止。Serial收集器是最基本发展最悠久的收集器。

缺点:

  • 垃圾回收的时候会停止所有的工作线程,影响用户的体验;

现在所有的垃圾收集器在进行垃圾回收的时候,都会暂停工作线程,只不过随着收集器的发展,停顿的时间越来越少。

优势:

  • 简单而高效(与其他收集器的单线程相比);

对于限定单个CPU的环境来说,Serial收集器没有线程交互的开销,可以专心的做垃圾收集工作,因而具有更高的效率。

适用场景

Serial收集器让仍然是JVM运行再Client模式下的默认的垃圾收集器。

  • 桌面应用场景

对于桌面应用来说,分配给jvm管理的内存一般不会很大,所以进行垃圾收集的时候,停顿的时间很短,只要不是频繁的发生GC,用户是完全可以接受的。所以Serial收集器对于运行在Client模式下的JVM来说,是一个很好的选择。

ParNew收集器

采用复制算法的新生代收集器,Serial收集器的多线程版本。

工作特点:

使用多条线程进行垃圾收集,除了使用多线程之外,其他的跟Serial收集器基本相同。ParNew收集器是许多运行在Server模式下的虚拟机首选的新生代的收集器。一个很重要的非性能的原因是因为,他是除了Serial外,唯一能跟CMS收集器配合使用的收集器。

比较

ParNew收集器,在单CPU环境中绝对不会有比Serial收集器更好的效果,甚至由于存在线程交互的开销,在两个CPU的工作环境下,其性能开销也可能不会比Serial更好。但是随着CPU数量的增加,ParNew收集器,对于GC时候,系统资源的有效利用还是很有好处的,默认开启的GC线程数跟CPU的数量相同,可以通过参数改变GC线程数量。

Parallel Scavenge收集器

采用复制算法的新生代收集器,是一种并行的多线程收集器。

工作特点:

Parallel Scavenge收集器跟其他收集器的关注点不同,其他收集器的关注点是尽可能的缩小用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量,Parallel Scavenge收集器也称为“吞吐量优先收集器”。

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

停顿时间越短就越适合与用户交互的程序,良好的响应速度能提升用户的体验。
高吞吐量则可以高效率的利用CPU的时间,尽可能快的完成程序的运算任务,主要适合在后台运行,而不需要与用户有太多的交互的任务。

除了关注点不同之外,Parallel
Scavenge收集器还有一个跟ParNew收集器的重要区别,那就是GC 自适应调节策略
通过设置Parallel Scavenge收集器的某一个开关参数,当这个参数打开后,就不需要手动的指定新生代的大小,Eden空间跟Survivor空间的比例,晋升老年代的对象年龄等细节参数了,虚拟机会自动根据当前系统的运行情况,收集性能监控信息,动态调整这些参数,以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应调节策略

Parallel Scavenge收集器提供了两个参数用于控制吞吐量,分别是控制最大垃圾收集停顿时间的参数和直接设置吞吐量的参数。需要注意的是,并不是最大的垃圾收集停顿时间越小,垃圾收集的速度越快,GC停顿时间缩短,是以牺牲吞吐量和新生代的大小换取的,系统把新生代调小些,收集300M新生代肯定比500M快,这也导致GC发生的更频繁,原来10秒收集一次,每次停顿100毫秒,现在5秒收集一次,每次停顿70毫秒。停顿时间的确下降了,但是吞吐量也下来了。

CMS收集器

CMS收集器是一款基于标记-清除算法的,以获取最短停顿时间为目标的老年代的收集器。

工作过程

  • 初始标记
  • 并发标记
  • 重新标记
  • 并发清除

初始标记:仅仅标记跟GC Roots直接关联的对象,速度很快;(需要停顿)
并发标记:进行GC Roots Tracing的过程;
重新标记:修正并发标记期间因为用户程序继续运作,而导致标记产生变动的那一部分对象的标记记录;(需要停顿,时间比初始标记长,但远远低于并发标记)

由于整个过程中耗时时间最长的并发标记跟并发清除过程,收集器线程都是跟用户线程一起工作的,所以整体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

优点

并发收集,低停顿;(比较适合那种追求高的响应速度,提高用户体验的应用)

缺点

  • 对CPU资源非常敏感;
  • 无法处理浮动垃圾,可能出现Concurrent Mode Failure失败而导致一次FULL GC的产生;
  • 收集结束后,会产生大量的碎片空间;

并发设计的程序对CPU资源都非常的敏感。在并发的阶段,虽然不会导致用户线程停顿,但是由于占用了一部分的CPU资源而导致应用程序变慢。CMS默认启动的回收线程数是(CPU数量+3)/4,如果CPU数量较多时侯还好,但是如果CPU的数量比较少(比如2个的时候),对应用程序的影响非常大,如果本来负载就很大,突然还需要分出来一半的资源给垃圾收集,导致应用程序的性能下降了超过一半,极大地降低了用户的体验。

由于CMS并发清理的阶段,用户线程仍然在运行,程序运行期间仍然会产生垃圾,这一部分垃圾出现在标记清除之后,CMS收集器无法在当次的收集中处理掉他们,只好留作下次GC处理,这一步垃圾就是浮动垃圾。正是因为垃圾收集的时候还可能产生浮动垃圾,所以在老年代,还需要为用户线程预留足够的内存空间,所以CMS收集器不能像其他收集器那样,等到老年代满了之后才进行垃圾回收。CMS收集器提供了参数,设置当老年代被占用了多少后,进行垃圾回收,如果参数设置过高的话,和容易导致大量的Concurrent Mode Failure失败,导致性能下降。当出现Concurrent Mode Failure的时候,jvm启动后备预案:启动Serial Old垃圾收集器,重新进行老年代的垃圾收集,这样停顿时间就很长了。

收集结束后,会产生大量的碎片空间,当出现过多碎片空间时侯,会提前触发FULL GC,为了解决这个问题,CMS收集器提供了一个参数,用于在收集器顶不住要进行FULL GC的时候,开启碎片的合并整理得过程,内存碎片整理得过程是无法并发执行的,但空间碎片问题解决了,但停顿时间不得不变长,还提供了一个参数,用于设置执行多少次不压缩的FULL GC后,执行一次带压缩的FULL GC。

适用场景

很大一部分的java应用集中在互联网站或者B/S系统的服务端,这类应用尤其重视服务的响应速度,希望停顿时间最短,以给用户带来更好的体验的应用场景。CMS收集器非常适合这类应用。

G1收集器

G1收集器是一款面向服务器端应用的垃圾收集器。

特点

  • 并发与并行,能充分利用多核多CPU的环境,最大限度的缩短用户的停顿时间,某些垃圾收集器需要停顿线程才能执行的垃圾收集操作,在G1收集器里仍然可以通过并发的方式让Java程序继续运行;
  • 分代收集(不需要其他收集器配合就可以管理整个java堆);
  • 空间整合,不会产生碎片空间;(整体看:标记-整理算法;局部看:复制算法。)
  • 可预测的停顿

可预测的停顿的实现原理

将Java堆划分成一个个大小相等的区域,对每一个区域进行监控,维持每一个列表,记录回收每一个区域的价值(回收后能获得的空间以及回收的代价),进行回收的时候,根据用户的需要,优先回收价值最大的那些区域。

工作过程

  • 初始标记;(需要停顿)
  • 并发标记;(并发执行)
  • 最终标记;(需要停顿,但是可并发执行)
  • 筛选回收;(可并发执行,但是只回收一部分region,而且时间是用户可控的,停顿用户线程将大幅度提高收集效率)

Region之间的对象引用和新生代与老年代之间的对象引用

在G1收集器中,Region之间的对象引用以及新生代和老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。在G1收集器中,每个Region都有一个与之对应的remembered set,虚拟机发现程序在对reference类型的数据进行写操作的时候,会检查reference引用的对象是否处于不用的region中,如果是,就把相关的引用信息记录到被引用对象所属的Region的remembered set之中。在进行内存回收的时候,在GC节点的枚举范围中加入remembered set即可保证不对全堆扫描也不会有遗漏。

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器