一、“并行”和“并发”的收集器
1.1、定义
并行(Parallel):并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在 协同工作,通常默认此时用户线程是处于等待状态。
并发(Concurrent):并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集 器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。
二、Serial收集器(新生代)
2.1、定义
Serial收集器是一款作用于新生代的串行垃圾收集器,使用的垃圾回收算法是:复制算法。一个单线程工作的收集器,在它进行垃圾收集时必须暂停其他所有工作线程(Stop The World),直到它收集结束。Serial收集器对于运行在客户端模式下的虚拟机来说是一个很好的选择。
Serial/Serial Old收集器的运行示意图
三、ParNew收集器(新生代)
3.1、定义
ParNew收集器是一款作用于新生代的并行垃圾收集器,使用的垃圾回收算法是:复制算法。实质上是 Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括 Serial 收集器可用的所有控制参数、收集算法Stop The World、对象分配规则、回收策略等都与 Serial 收集器完全一致。ParNew收集器是运行在服务端模式下的 HotSpot 虚拟机。
ParNew/Serial Old收集器运行示意图
除了Serial 收集器外,目前只有ParNew收集器与 CMS 收集器配合工作。ParNew 收集器是激活CMS后(使用-XX+UseConcMarkSweepGC 选项)的默认新生代收集器,也可以使用-XX:+/-UseParNewGC项来强制指定或者禁用它。
四、ParallelScavenge 收集器(新生代)
4.1、定义
ParallelScavenge收集器是一款作用于新生代的并行垃圾收集器,使用的垃圾回收算法是:复制算法。Parallel Scavenge 收集器的特点是它的关注点与其他收集器不同,CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而 Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量 (Throughput),也经常被称作“吞吐量优先收集器”。所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值,即:
运行用户代码时间
吞吐量 = --------------------------------
运行用户代码时间+运行垃圾收集时间
4.2、相关参数设置
Parallel Scavenge 收集器提供了两个参数用于精确控制吞量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMilis 参数、直接设置吞吐最大小的-XX:GCTimeRatio 参数以及设置自适应大小策略的-XX:+UseAdaptiveSizePolicy参数。
-XX:MaxGCPausMillis(最大垃圾收集停顿时间):允许一个大于0的毫秒数,收集器将尽力保证内存回收花费的时间不超过用户设定值。不过大家不要异想天开地认为如果把这个参数的值设置得更小一点就能使得系统的垃圾收集速度变得更快,垃圾收集停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的:系统把新生代调得小一些,收集 300MB 新生代肯定比收集 500MB 快,但这也直接导致垃圾收集发生得更频繁,原来 10秒收集一次、每次停顿100毫秒,现在变成5秒收集一次、每次停顿 70毫秒。停顿时间的确在下降,但吞吐量也降下来了。
-XX:GCTimeRatio(设置吞吐最大小):参数的值应设置为一个正整数,表示用户期望虚拟机消耗在 GC上的时间不超过程序运行时间的 1/(1+N)。默认值为 99,含义是尽可能保证应用程序执行的时间为收集器执行时间的99倍,也即收集器的时间消耗不超过总运行时间的 1%。
-XX:+UseAdaptiveSizePolicy:这是一个开关参数,当这个参数被激活之后,就不需要人工指定新生代的大小(-Xmn)Eden 与Survivor 区的比例(-XXSurvivorRatio)、晋升老年代对象大小(-XXPretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。这种调节方式称为垃圾收集的自适应的调节策略(GC Ergonomics)。只需要把基本的内存数据设置好(如-Xmx 设置最大堆)然后使用-XX:MaxGCPauseMillis 参数(更关注最大停顿时间)或-XX:GCTimeRatio(更关注吐量)参数给虚拟机设立一个优化目标,那具体细节参数的调节工作就由虚拟机完成了。自适应调节策略也是 Parallel Scavenge 收集器区别于 ParNew 收集器的一个重要特性。
五、Serial Old收集器(老年代)
5.1、定义
Serial Old收集器是一款作用于老年代的串行垃圾收集器,使用的垃圾回收算法是:标记-整理算法。这个收集器的主要意义也是供客户端模式下的 HotSpot 虚拟机使用。如果在服务端模式下,它也可能有两种用途:一种是在JDK 5 以及之前的版本中与 Parallel Scavenge 收集器搭配使用,另外一种就是作为 CMS 收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure 时使用。
Serial/Serial Old收集器的运行示意图
六、Parallel Old收集器(老年代)
6.1、定义
Parallel Old收集器是一款作用于老年代的并行垃圾收集器,使用的垃圾回收算法是:标记-整理算法。直到Parallel old 收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的搭配组合,在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑 Parallel Scavenge 加ParallelOld 收集器这个组合。
Parallel Scavenge/Parallel Old收集器运行示意图
七、CMS( Concurrent Mark Sweep) 收集器(老年代)
7.1、定义
CMS收集器是一款作用于老年代的并行垃圾收集器,使用的垃圾回收算法是:标记-清除算法。CMS 收集器是一种以获取最短回收停顿时间为目标的收集器。
7.2、运行步骤
CMS运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为四个步骤包括:
初始标记(CMS initial mark) :需要“Stop The World”,仅仅只是标记一下GC Roots 能直接关联到的对象,速度很快;
并发标记(CMS concurrent mark) :从 GC Roots 的接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行;
重新标记(CMS remark):需要“Stop The World”,为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录(CMS用的是:增量更新,G1用的是:原始快照),这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短;
并发清除(CMS concurrent sweep):清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
由于在整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一起工作,所以从总体上来说,CMS 收集器的内存回收过程是与用户线程一起并发执行的。
Concurrent Mark Sweep 收集器运行示意图
7.3、缺点
CMS 收集器是 HotSpot 虚拟机追求低停顿的第一次成功尝试,但是它还远达不到完美的程度,至少有以下三个明显的缺点;
CMS 收集器对处理器资源非常敏感。事实上,面向并发设计的程序都对处理器资源比较敏感。在并发阶段,它虽然不会导致用户线程停顿,但却会因为占用了一部分线程(或者说处理器的计算能力) 而导致应用程序变慢,降低总吞叶量。CMS 默认启动的回收线程数是(处理器核心数量 +3)/4,也就是说,如果处理器核心数在四个或以上,并发回收时垃圾收集线程只占用不少于 25% 的处理器运算资源,并且会随着处理器核心数量的增加而下降。但是当处理器核心数量不足四个时,CMS 对用户程序的影响就可能变得很大。如果应用本来的处理器负载就很高,还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然大幅降低。为了缓解这种情况,虚拟机提供了一种称为“增量式并发收集器”(Incremental Concurrent Mark Sweep/i-CMS)的 CMS 收集器变种,所做的事情和以前单核处理器年代 PC 机操作系统靠抢占式多任务来模拟多核并行多任务的思想一样,是在并发标记、清理的时候让收集器线程、用户线程交替运行,尽量减少垃圾收集线程的独占资源的时间,这样整个垃圾收集的过程会更长,但对用户程序的影响就会显得较少一些,直观感受是速度变慢的时间更多了,但速度下降幅度就没有那么明显。实践证明增量式的 CMS 收集器效果很一般,从JDK 7开始,i-CMS 模式已经被声明为“deprecated”,即已过时不再提倡用户使用,到JDK 9 发布后i-CMS 模式被完全废弃。
由于CMS 收集器无法处理“浮动垃圾”(Floating Garbage),有可能出现“Concurrent Mode Failure”失败进而导致一次完全“Stop The World”的 Full GC 的产生在CMS 的并发标记和并发清理阶段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后CMS 无法在当次收集中处理掉它们,只好留待下一次垃圾收集时再清理掉。这一部分垃圾就称为“浮动垃圾”。同样也是由于在垃圾收集阶段用户线程还需要持续运行,那就还需要预留足够内存空间提供给用户线程使用,因此 CMS 收集器不能像其他收集器那样等待到老年代几平完全被填满了再进行收集,必须预留一部分空间供并发收集时的程序运作使用。在JDK 5的默认设置下,CMS 收集器当老年代使用了 68% 的空间后就会被激活,这是一个偏保守的设置,如果在实际应用中老年代增长并不是太快,可以适当调高参数-XX:CMSInitiatingOccu-pancyFraction 的值来提高 CMS 的触发百分比,降低内存回收频率,获取更好的性能。到了JDK 6 时,CMS 收集器的启动值就已经默认提升至 92%.但这又会更容易面临另一种风险: 要是 CMS 运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次“并发失败”(Concurrent Mode Failure),这时候虚拟机将不得不启动后备预案:冻结用户线程的执行,临时启用 Serial old 收集器来重新进行老年代的垃圾收集,但这样停顿时间就很长了。所以参数 -XX:CMSInitiatingOccupancyFraction 设置得太高将会很容易导致大量的并发失败产生,性能反而降低,用户应在生产环境中根据实际应用情况来权衡设置。
CMS 是一款基于“标记一清除”算法的垃圾收集器。这意味着收集结束时会出现大量空间碎片产生。空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很多剩余空间,但就是无法找到足够大的连续空间来分配当前对象,而不得不而触发一次 Ful GC的情况。为了解决这个问题,CMS 收集器提供了一个-XX:+UseCCompactArFulCollection 开关参数(默认是开启的,此参数从JDK 9开始废弃),用于CMS 收集器不得不进行FUl GC 时开启内存碎片的合并整理过程,由于这个内存整理必须动在活对象,(在 Shenandoah 和ZGC 出现前)是无法并发的。这样空间碎片问题是解决但停顿时间又会变长,因此虚拟机设计者们还提供了另外一个参数-XX: CMSFuIGCsBeforCompaction (此参数从DK 9 开始废弃),这个参数的作用是要求 CMS 收集器在执行过F次(数量由参数值决定)不整理空间的 Full GC 之后,下一次进入 Full GC 前会先进行片整理(默认值为0,表示每次进入 Full GC时都进行碎片整理)。
八、Garbage First收集器
8.1、定义
由于G1收集器比较复杂,留到下个篇章介绍。
借道友法力一用:
========================== stay hungry stay foolish =============================