一、经典垃圾收集器

  概念:

  如果说收集算法是回收内存的方法论,那么垃圾收集器就是内存回收的实践者。不同的虚拟机一般都会提供各种参数供用户根据自己应用特点和要求组合出各个内存分代所使用的收集器。各款收集器之间的关系如图3-6所示,young generation标识年轻代的垃圾收集器,tenured generation标识老年代的垃圾收集器,G1表示年轻代和老年代都适用,而JDK9表示,Serial+CMS和ParNew+Serial Old组合到了JDK9之后不再被支持。

  1.1 Serial收集器

  Serial收集器是最基础,历史最悠久的收集器,它是一个单线程工作的收集器,但是它的单线程的意义并不是仅仅说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的在于强调它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束,这个过程被称之为“Stop The World”,即STW。在服务端现在虚拟机正常情况下基本不再使用Serial垃圾收集器回收内存了,但是对于某些运行在客户端的虚拟机,Serial收集器依然是新生代默认的收集器。主要原因在于和其他垃圾收集器比起来,它简单而高效。它是所有收集器里额外内存占用最小的;对于单核处理器或者处理器核心数较少的环境来说,Serial收集器由于没有线程交互开销,专心做垃圾收集自然可以获得最高的单线程收集效率。在一些用户桌面应用,分配给虚拟机管理的内存并会特别大,收集几十兆甚至几百兆,Serial垃圾收集器完全可以控制在即几十到一百多毫秒之内,只要不是频繁垃圾收集,这个停顿对用户来说完全是可以接受的,因此Serial垃圾收集器对于运行在客户端模式下的虚拟机是一个不错的选择。

  1.2 ParNew收集器

  ParNew收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括Serial收集器可用的所有控制参数、收集算法、STW、对象分配规则、回收策略等都与Serial收集器完全一致,在实现上这两种收集器也公用了相当多的代码。如图3-8所示。
  ParNew的诞生有一个与功能、性能无关但其实很重要的原因是:除了Seriral收集器之外,目前只有它能与CMS收集器配合工作。在ParNew还没诞生之前,CMS只能与Serial进行配合工作。在JDK5中,当使用CMS来作为老年代的收集器时,新生代默认采用的收集器是ParNew。而到了JDK9,ParNew+CMS不再是官方推荐的服务端模式下的收集器解决方案,官方希望它能被G1代替。
  ParNew收集器在单核心处理器的环境中绝对不会比Serial收集器有更好的效果,主要是因为存在线程交互的开销。但是随着处理器核心线程数的增加,ParNew对垃圾收集时系统资源的高效利用还是很有好处的,它默认开启的收集线程数与处理器核心数量相同。

  1.3 Parallel Scavenge收集器

  Parallel Scavenge收集器也是一块新生代收集器,采用“标记-复制”算法,诸多特性从表面上看和ParNew非常相似。Parallel Scavenge(以下简称PS)的特点和关注点与其它啊收集器不同,CMS等手机其关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而PS收集器的目标更侧重吞吐量的控制,并提供了对用的配置参数。

吞吐量=运行用户代码时间/(运行用户代码时间+运行垃圾收集时间)

  PS提供了两个参数用来靳准控制吞吐量,分别是控制最大垃圾收集停顿时间:-XX:MaxGCPauseMillis参数,和直接设置吞吐量大小的-XX:GCTimeRatio参数。
  -XX:MaxGCPauseMillis参数允许值是一个大于0的毫秒数,收集器将尽量保证在这个时间段完成垃圾收集,但不是这个数字越小越好,垃圾收集停顿时间是以牺牲吞吐量和新生代空间为代价换取的:系统把新生代调小,收集300M肯定比500M块,但这样导致垃圾收集频率更加频繁,原来10秒收集一次停顿100毫秒,现在5秒收集一次,停顿70毫秒。频率增加这样总的垃圾收集时间也在增加,导致整体的吞吐量其实是在下降的。
  -XX:GCTimeRatio的参数值应当是一个大于0小于100的整数,表示希望在GC花费不超过应用程序执行时间的1/(1+n)。换句话说,此参数的值表示运行用户代码时间是GC运行时间的n倍。默认值是99,即允许最大1%的垃圾收集时间。
  PS还提供了一个参数-XX:+UseAdaptiveSizePolicy,这是一个开关参数,开启后,就不需要人工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SuvivorRatio)、晋升老年代对象的大小(-XX:PretenureSizeThreshold)等细节参数,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。这种自动调节称为垃圾收集的自适应的调节策略,这也是PS收集器区别于ParNew收集器的一个重要特性。

  1.4 Serial Old 收集器

  Serial Old是Serial收集器的老年代版本,同样是一个单线程收集器,采用“标记-整理”算法。这个收集器也是供客户端模式先JVM使用的。如果服务端,它一般是两种用途:JDK5及以前与PS收集器配合使用,另外一种是CMS收集器发生失败后的备案,当CMS收集失败后,采用Serial Old备选。

  1.5 Parallel Old 收集器

  Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程收集,基于“标记-整理”实现。Parallel Old的出现,其实主要也是为了弥补搭配Parallel Scavenge老年代上收集器的不足,在JDK6之前,如果选择了Parallel Scavenge,那么老年代只有Serial Old一种选择,由于单线程的老年代收集器无法充分利用服务器多处理器的并行处理能力,在老年代内存空间很大而且硬件规格比较高级的运行环境中,Parallel+Serial Old的总吞吐量还不及ParNew+CMS组合来得优秀。
  在注重吞吐量或者处理器资源较为稀缺的场合,可以优先考虑使用Parallel+Parallel Old收集器组合。