1、Serial收集器:
垃圾收集算法:新生代采取标记-复制,老年代采取标记整理算法。
是HotSpot虚拟机jdk1.3.0前新生代收集器的唯一选择,最基础,历史最悠久。
单线程:并不是说他在进行垃圾收集时,仅仅使用一个处理器或一条收集线程,而是进行垃圾收集时,必须要暂停用户所有的其他线程,也就是Stop The World。
Stop The World:这项工作由虚拟机在后台自动发起和自动完成,用户毫不知情的,自己的应用程序就暂停了。
但又合乎情理,就像你妈妈给你打扫卫生,让你从房间出来,不能你妈妈边打扫,你边扔吧。
优点:似乎已经没用了,但是到现在他依然是HotSpot虚拟机运行在客户端模式下的默认新生代收集器,简单高效,对于资源受限的环境,额外内存(保证垃圾搜集器正常运行而存储的额外信息)消耗最小,没有线程交互的开销,单线程收集效率最高。
使用场景:对于桌面应用程序和微服务应用,分配给虚拟机管理的内存较小,停顿的时间可以控制在十几毫秒内,最多一百多毫秒,只要不是频繁地进行垃圾收集,用户完全可以接受,Serial对于运行在客户端模式下的虚拟机是一个不错的选择。
2、ParNew收集器
垃圾收集算法:新生代采取标记-复制算法,老年代采取标记整理算法。
Serial收集器的关系:是Serial的多线程并行版,多条线程进行垃圾回收,其他的和Serial几乎完全一致。
是不少运行在服务端模式下的HotSpot虚拟机(尤其是jdk7以前的遗留系统中)首选的新生代收集器,其中一个重要的原因就是除了Serial外,目前只有他能和CMS(下面的一款收集器)配合工作。
CMS和G1
CMS收集器,作为老年代收集器,jdk5发布时推出的,真正意义上的支持并发的收集器,首次让垃圾收集线程和用户线程(基本上)同时工作。ParNew是激活CMS(使用-XX:UseConcMarkSweepGC参数)后的默认新生代收集器,可以使用-XX:UseParNewGC参数禁止使用ParNew或强制指定。
但是G1的推出,G1不需要与新生代的垃圾收集器配合,官方希望他取代CMS+ParNew的组合,并且取消了-XX:UseParNewGC参数(该参数可以禁止使用ParNew),意味着ParNew从此只能和CMS配合使用。
缺点:单核心处理器环境下绝对不会比Serial的效果好,存在线程交互的开销。
3、Parallel Scavenge收集器
垃圾收集算法:基于标记-复制算法实现.
关注点:与ParNew很多特性相似,但是他的关注点和其他的不同,他关注的是达到一个可以控制的吞吐量(Througput),前面的收集器关注的是减少用户线程的停顿时间。
吞吐量:
吞吐量:处理器用于运行用户代码的时间与处理器总耗时间的比值。
吞吐量=运行用户代码时间/(运行用户代码时间 + 运行垃圾收集时间)
使用场景:
更适合用户交互时提高响应的速度,高吞吐量则可以最高效率的利用处理器资源,尽快完成程序的运算任务,主要适合在后台运算而不需要进行太多交互的分析任务。
两个控制吞吐量的参数:控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数,以及直接设置吞吐量大小的-XX:GCTimeRatio参数。
-XX:MaxGCPauseMillis参数理解:一个大于0的毫秒数,收集器将尽力保证内存回收花费的时间不超过用户设定值,不要异想天开地认为如果把这个参数的值设置得更小一点就能使得系统的垃圾收集速度变得更快,垃圾收集停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的,收集300MB新生代肯定比收集500MB快,但这也直接导致垃圾收集发生得更频繁,原来10秒收集一次、每次停顿100毫秒,现在变成5秒收集一次、每次停顿70毫秒。停顿时间的确在下降,但吞吐量也降下来了。
-XX:GCTimeRatio参数理解:的值则应当是一个大于0小于100的整数,也就是垃圾收集时间占总时间的 比率,相当于吞吐量的倒数。譬如把此参数设置为19,那允许的最大垃圾收集时间就占总时间的5%
(即1/(1+19)),默认值为99,即允许最大1%(即1/(1+99))的垃圾收集时间。
垃圾收集的自适应的调节策略(GC Ergonomics):Parallel Scavenge收集器还有一个参数-XX:+UseAdaptiveSizePolicy值得我们关注。这是一 个开关参数,当这个参数被激活之后,就不需要人工指定新生代的大小(-Xmn)、Eden与Survivor区 的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时 间或者最大的吞吐量。
我们只需要把基本的内存数据设置好(如-Xmx设置最大堆),然后使用-XX:MaxGCPauseMillis参数(更关注最大停顿时间)或- XX:GCTimeRatio(更关注吞吐量)参数给虚拟机设立一个优化目标,那具体细节参数的调节工作就由虚拟机完成了。
4、Serial Old收集器
是Serial的老年代版本,是一个单线程收集器,使用标记-整理算法。
如果在服务端模式下,它也可能有两种用 途:一种是在JDK 5以及之前的版本中与Parallel Scavenge收集器搭配使用,另外一种就是作为CMS 收集器发生失败时的后备预案(后面有讲到),在并发收集发生Concurrent Mode Failure时使用。
注意:Parallel Scavenge中其实有PS MarkSweep进行老年代收集,并不是直接调用Serial Old,但是PS MarkSweep和Serial Old的实现几乎一样,所以许多资料使用Serial Old代替PS MarkSweep进行讲解。
5、Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实
现。这个收集器是直到JDK 6时才开始提供的。
了解:在jdk6前,新生代的Parallel Scavenge收集器处于尴尬的状态,如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old(PS MarkSweep)收集器以外别无选择,其他表现良好的老年代收集器,如CMS无法与它配合工作。由于老年代Serial Old收集器在服务端应用性能上的“拖累”,使用Parallel Scavenge收集器也未必能在整体上 获得吞吐量最大化的效果。同样,由于单线程的老年代收集中无法充分利用服务器多处理器的并行处 理能力,在老年代内存空间很大而且硬件规格比较高级的运行环境中,这种组合的总吞吐量甚至不一 定比ParNew加CMS的组合来得优秀。
6、CMS收集器(重点)
1、简介
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。停顿时间较短,适合网站和浏览器,给用户带来良好的交互体验。
Mark Sweep:顾名思义,标记-清除,基于标记-清除算法实现。
2、运作过程:
-
初始标记(CMS initial mark)
初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。
-
并发标记(CMS concurrent mark)
并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。 -
重新标记(CMS remark)
重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的 标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一 些,但也远比并发标记阶段的时间短。 -
并发清除(CMS concurrent sweep)
清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
初始标记、重新标记这两个步骤仍然需要“Stop The World”。 耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一起工作,所以从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
3、优点
并发收集、低停顿,一些官方公开文档里面也称之为“并发低停顿收集器”(Concurrent Low Pause Collector)。
4、缺点:
1、CMS收集器对处理器资源非常敏感。在并发阶段,它虽然不会导致用户线程停顿,但却会因为占用了一部分线程(或者说处理器的计算能力)而导致应用程序变慢,降低总吞吐量。应用本来的处理器负载就很高,还要分出一部分运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然大幅降低。
为了解决这一问题,虚拟机提供了增量式并发收集器”(Incremental Concurrent Mark Sweep/i-CMS)的CMS收集器变种,就是在并发标记、清理的时候让收集器线程、用户线程交替运行,尽量减少垃圾收集线程的独占资源的时间,这样整个垃圾收集的过程会更长,但对用户程序的影响就会显得较少一些,直观感受是速度变慢的时间更多了,但速度下降幅度就没有那么明显。
总结:对处理器资源非常敏感,不会导致用户线程停顿,但却会因为占用了一部分线程(或者说处理器的计算能力)而导致应用程序变慢。
2、CMS收集器无法处理“浮动垃圾”(FloatingGarbage),在CMS的并发标记和并发清理阶段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留待下一次垃圾收集时再清理掉。在垃圾收集阶段用户线程还需要持续运行,就需要预留足够内存空间提供给用户线程使用,因此CMS收集器不能像其他收集器那样等待到老年代几乎完全被填满了再进行收集,必须预留一部分空间供并发收集时的程序运作使用。在JDK5的默认设置下,CMS收集器当老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果在实际应用中老年代增长并不是太快,可以适当调高参数-XX:CMSInitiatingOccu-pancyFraction的值来提高CMS的触发百分比,降低内存回收频率,获取更好的性能。到了JDK6时,CMS收集器的启动阈值就已经默认提升至92%。但这又会更容易面临另一种风险:要是CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次“并发失败”(Concurrent Mode Failure),这时候虚拟机将不得不启动后备预案:冻结用户线程的执行,临时启用Serial Old收集器来重新进行老年代的垃圾收集,但这样停顿时间就很长了。所以参数-XX:CMSInitiatingOccupancyFraction设置得太高将会很容易导致大量的并发失败产生,性能反而降低,用户应在生产环境中根据实际应用情况来权衡设置。
总结:无法清理浮动垃圾,因为在并发标记和并发清清除的过程中,用户线程还在继续运行,就会产生垃圾,但是这两个部分是在初始标记后才产生的,在本次收集的过程中不会被清除,同时,因为用户线程还在运行,就要预留内存给用户使用。
3、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时都进行碎片整理)
总结:基于标记-清除算法实现,会有大量的空间碎片产生。
- 还有一个更重要的收集器就是Garbage First收集器,简称G1,未完待续…