JVM - 垃圾收集器(一)

HotSpot虚拟机垃圾收集器的种类以及作用空间(连线表示两个收集器可以搭配使用)

Serial 收集器:该收集器单线程工作,在gc时暂停用户其他所有的工作线程即“Stop The World”,新生代采用复制算法,老年代采用 标记-整理 算法,是虚拟机运行在Client模式下默认的新生代收集器,简单高效。

Serial/Serial Old 收集器运行示意图

ParNew 收集器:该收集器是Serial 收集器的多线程版本,也是除了Serial收集器之外可以与CMS收集器配合工作的收集器。是运行在 Server模式下 的虚拟机中首选的新生代收集器。ParNew 收集器也是使用 -XX:UseConcMarkSweepGC 选项后的默认新生代收集器,也可以使用 -XX:UseParNewGC 选项来强制指定它。

ParNew/Serial Old收集器运行示意图

Parallel Scavenge 收集器:该收集器是一个新生代收集器,它也是采用 复制算法 的收集器,又是 并行 的多线程收集器。从两个点了解该收集器即可:1. 吞吐量;2. 自适应策略。

吞吐量:Parallel Scavenge 收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即 吞吐量 = 运行用户代码时间/(运行用户代码时间 + 垃圾收集时间)。高吞吐量可以高效的利用CPU时间。

Parallel Scavenge 收集器提供两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMillis 参数以及直接设置吞吐量大小的 -XX:GCTimeRatio 参数。MaxGCPauseMillis参数允许的值是一个大于0的毫秒数,收集器尽可能地保证内存回收花费的时间不超过设定的值。GCTimeRatio 参数的值是一个大于0且小于100的值,也就是垃圾收集时间占总时间的比率。

自适应策略:Parallel Scavenge 收集器通过打开 -XX:+UseAdaptiveSizePolicy 参数不需要手工指定新生代的大小(-Xmm)、Eden与Survivor区的比例(-XX:SUrvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大吞吐量这种调节反射光辉成为GC自适应的调节策略。如果对虚拟机收集器运作原理不了解,使用该参数是一个比较好的选择。

Serial Old 收集器:是Serial收集器的老年代版本,同样是一个单线程收集器,使用标记-整理算法,给Client模式下运行的虚拟机使用。如果在Server模式下使用该收集器那么它有两种用途:1. 在jdk1.5以及之前的版本中搭配Parallel Scavenge 收集器使用;2. 作为 CMS收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。

Serial/Serial Old 收集器运行示意图

 Parallel Old 收集器:是Parallel Scavenge 收集器的老年代版本,使用多线程和标记-整理算法。

Parallel Scavenge / Parallel Old 收集器运行示意图

CMS 收集器:CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。基于标记-清除算法实现。整个运作过程分为四步。

初始标记(CMS initial mark)

并发标记(CMS concurrent mark)

重新标记(CMS remark)

并发清除(CMS concurrent sweep)

其中,初始标记和重新标记这两个步骤仍然需要 “Stop The World”,初始标记仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,并发标记阶段就是 进行 GC Roots Tracing 的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停段时间一般比初始标记阶段稍长一点,但远比并发标记时间短。由于这整个过程中耗时最长的并发标记和并发清除过程收集器的线程可以与用户线程一起工作,所以从整体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。(图片来自百度)

 CMS 收集器的优点:并发收集、低停顿。

CMS 收集器的缺点:

  • CMS 收集器对CPU资源非常敏感。其实,面向并发设计的程序都对CPU资源比较敏感。在并发阶段,它虽然不会导致用户线程停顿,但是因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量降低。CMS默认启动的回收线程数是(CPU数量+3)/4,也就是当CPU在4个以上时,并发回收时垃圾县昵称不少于25%的CPU资源,并且随着CPU的数量增加而下降。但是当CPU不足4个(譬如2个)时,CMS对用户的影响可能变得很大,如果本来CPU负载比较大,还分出一半的运算能力去执行收集线程,就可能导致用户程序的执行速度忽然降低了50%。为了应付这种情况,虚拟机提供了一种“增量式并发收集器”(i-CMS),但是实践证明效果一般,已经弃用。 
  • CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次 Full GC 的产生。由于CMS并发清理阶段用户线程还在运行着,伴随程序的运行自然就还会有新的垃圾产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下次GC时在清理掉。这一部分垃圾就成为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留足够的空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎塞满了在进行垃圾收集,需要预留一部分空间提供并发收集时的程序运作使用。在JDK1.5的默认设置下,CMS收集器当老年代使用了 68% 的空间后就会被激活,这是一个偏保守的设置,如果应用中老年代增长的不是太快,可以适当调高参数 -XX:CMSInitiatingOccupancyFraction 的值来提高触发百分比,以便降低内存回收的次数从而获取更好的性能,在JDK1.6中,CMS收集器的启动阈值已经提升至92%。要是CMS运行期间预留的内存无法满足程序的需要时,就会出现一次“Concurrent Mode Failure”失败,这是虚拟机将启动后备预案,临时启用 Serial Old 收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数 -XX:CMSInitiatingOccupancyFraction 设置的太高很容易导致大量“Concurrent Mode Failure”失败,性能反而降低。
  • CMS因为采用标记-清除算法实现,会产生内存空间碎片。CMS是一款基于 标记-清除 算法实现的收集器,收集结束时会产生大量内存空间碎片。空间碎片过多时,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续内存空间来分配当前对象,不得不提前触发一次 Full GC。为了解决这个问题虚拟机提供了一个 -XX:+UseCMSCompactAtFullColletion 开关参数(默认开启),用于在CMS收集器顶不住要进行Full GC时开启内存碎片的合并整理过程,内存整理的过程是无法并发的,空间碎片没有了,但停顿时间变长了。虚拟机还提供了另外一个参数 -XX:CMSFullFCsBeforeCompaction ,这个参数的作用是设置执行多少次不压缩的 Full GC后,跟着来一次带压缩的(默认值0,表示每次进入 Full GC时都进行碎片整理)

猜你喜欢

转载自blog.csdn.net/qq_29676623/article/details/84472570