一、Serial收集器
Serial收集器是最基础、历史最悠久的收集器,曾经(在JDK 1.3.1之前)是HotSpot虚拟机新生代 收集器的唯一选择。大家只看名字就能够猜到,这个收集器是一个单线程工作的收集器,但它的“单线程”的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。
对于内 存资源受限的环境,它是所有收集器里额外内存消耗最小的;对于单核处理器或处理器核心数较少的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。
二、ParNew收集器
ParNew收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之
外,其余的行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX:
PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一致,在实现上这两种收集器也共用了相当多的代码。
ParNew收集器除了支持多线程并行收集之外,其他与Serial收集器相比并没有太多创新之处,但它
却是不少运行在服务端模式下的HotSpot虚拟机,尤其是JDK 7之前的遗留系统中首选的新生代收集器,其中有一个与功能、性能无关但其实很重要的原因是:除了Serial收集器外,目前只有它能与CMS收集器配合工作。
三、Parallel Scavenge收集器
Parallel Scavenge收集器也是一款新生代收集器,它同样是基于标记-复制算法实现的收集器,也是 能够并行收集的多线程收集器……Parallel Scavenge的诸多特性从表面上看和ParNew非常相似,那它有什么特别之处呢?Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间
的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。
使用Parallel Scavenge收集器配合自适应调节策
略,把内存管理的调优任务交给虚拟机去完成也许是一个很不错的选择。只需要把基本的内存数据设置好(如-Xmx设置最大堆),然后使用-XX:MaxGCPauseMillis参数(更关注最大停顿时间)或-XX:GCTimeRatio(更关注吞吐量)参数给虚拟机设立一个优化目标,那具体细节参数的调节工作就由虚拟机完成了。自适应调节策略也是Parallel Scavenge收集器区别于ParNew收集器的一个重要特性。
四、Serial Old收集器
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。这个收集器的主要意义也是供客户端模式下的HotSpot虚拟机使用。
它也可能有两种用 途:一种是在JDK 5以及之前的版本中与Parallel Scavenge收集器搭配使用[1],另外一种就是作为CMS收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用。
五、Parallel Old收集器
Parallel Old
是
Parallel Scavenge
收集器的老年代版本,支持多线程并发收集,基于标记
-
整理算法实 现。这个收集器是直到JDK 6
时才开始提供的,在此之前,新生代的
Parallel Scavenge
收集器一直处于相 当尴尬的状态,原因是如果新生代选择了Parallel Scavenge
收集器,老年代除了
Serial Old
(
PSMarkSweep)收集器以外别无选择,其他表现良好的老年代收集器,如
CMS
无法与它配合工作。由于老年代Serial Old
收集器在服务端应用性能上的
“
拖累
”
,使用
Parallel Scavenge
收集器也未必能在整体上获得吞吐量最大化的效果。同样,由于单线程的老年代收集中无法充分利用服务器多处理器的并行处理能力,在老年代内存空间很大而且硬件规格比较高级的运行环境中,这种组合的总吞吐量甚至不一 定比ParNew
加
CMS
的组合来得优秀。 直到Parallel Old
收集器出现后,
“
吞吐量优先
”
收集器终于有了比较名副其实的搭配组合,在注重 吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge
加
Parallel Old
收集器这个组合
六、CMS收集器
CMS(
Concurrent Mark Sweep
)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java
应用集中在互联网网站或者基于浏览器的
B/S
系统的服务端上,这类应用通常都会较为 关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。CMS
收集器就非常符合这类应用的需求。整个过程分为四个步骤
(1)初始标记(
CMS initial mark
)
(2)并发标记(
CMS concurrent mark
)
(3)重新标记(
CMS remark
)
(4)并发清除(
CMS concurrent sweep
)
其中初始标记、重新标记这两个步骤仍然需要“Stop The World”
。初始标记仅仅只是标记一下
GC Roots能直接关联到的对象,速度很快;并发标记阶段就是从
GC Roots
的直接关联对象开始遍历整个对 象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行;而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短;最后是并发清除阶段,清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一
起工作,所以从总体上来说,
CMS
收集器的内存回收过程是与用户线程一起并发执行的。
CMS触发条件:
1、如果没有设置-XX:+UseCMSInitiatingOccupancyOnly,虚拟机会根据收集的数据决定是否触发(建议线上环境带上这个参数,不然会加大问题排查的难度)。 2、老年代使用率达到阈值 CMSInitiatingOccupancyFraction
,默认92%。 3、永久代的使用率达到阈值 CMSInitiatingPermOccupancyFraction
,默认92%,前提是开启 CMSClassUnloadingEnabled
。 4、新生代的晋升担保失败。
晋升担保失败
老年代是否有足够的空间来容纳全部的新生代对象或历史平均晋升到老年代的对象,如果不够的话,就提早进行一次老年代的回收,防止下次进行YGC的时候发生晋升失败。
CMS优点:
CMS
是一款优秀的收集器,它最主要的优点在名字上已经体现出来:并发收集、低停顿,一些官
方公开文档里面也称之为
“
并发低停顿收集器
”
(
Concurrent Low Pause Collector
)。
CMS
收集器是HotSpot虚拟机追求低停顿的第一次成功尝试
CMS缺点:
(1)首先,CMS 收集器对处理器资源非常敏感。事实上,面向并发设计的程序都对处理器资源比较敏感。在并发阶段,它虽然不 会导致用户线程停顿,但却会因为占用了一部分线程(或者说处理器的计算能力)而导致应用程序变慢,降低总吞吐量。
(2)CMS收集器无法处理 “ 浮动垃圾 ” ( Floating Garbage ),有可能出现 “Con-current Mode
Failure”
失败进而导致另一次完全
“Stop The World”
的
Full GC
的产生。在
CMS
的并发标记和并发清理阶 段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分 垃圾对象是出现在标记过程结束以后,CMS
无法在当次收集中处理掉它们,只好留待下一次垃圾收集时再清理掉。这一部分垃圾就称为“
浮动垃圾
”
。
(3)还有最后一个缺点CMS 是一款基于 “ 标记 - 清除 ”算法实现的收集器就可能想到这意味着收集结束时会有大量空间碎片产生。空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很多剩余空间,但就是无法找到足够大的连续空间来分配当前对象,而不得不提前触发一次Full GC 的情况。
CMS的碎片解决方案
CMS是一款基于“标记-清除”算法实现的收集器,如果读者对前面这部分介绍还有印象的话,就可能想到这意味着收集结束时会有大量空间碎片产生。空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很多剩余空间,但就是无法找到足够大的连续空间来分配当前对象,而不得不提前触发一次Full GC的情况。为了解决这个问题, CMS收集器提供了一个-XX:+UseCMS-CompactAtFullCollection开关参数(默认是开启的,此参数从JDK 9开始废弃),用于在CMS收集器不得不进行Full GC时开启内存碎片的合并整理过程,由于这个内存整理必须移动存活对象,(在Shenandoah和ZGC出现前)是无法并发的。这样空间碎片问题是解决了,但停顿时间又会变长, 因此虚拟机设计者们还提供了另外一个参数-XX:CMSFullGCsBefore-Compaction(此参数从JDK 9开始废弃),这个参数的作用是要求CMS收集器在执行过若干次(数量由参数值决定)不整理空间的Full GC之后,下一次进入Full GC前会先进行碎片整理(默认值为0,表示每次进入Full GC时都进行碎片整理)。