主要介绍HotSpot虚拟机的垃圾收集器,这个虚拟机包含的所有收集器如图所示:
可以看到,收集器之间是可以搭配使用的。下面介绍这些收集器的特性、基本原理和使用场景。在介绍之前先明确一个观点:直到现在为止还没有最好的收集器出现,更加没有万能的收集器,选择的是对具体应用最合适的收集器。
串行收集器是最基本、发展历史最悠久的收集器。它们的特点就是单线程运行及独占式运行,因此会带来很不好的用户体验。虽然它的收集方式对程序的运行并不友好,但由于它的单线程执行特性,应用于单个CPU硬件平台的性能可以超过其他的并行或并发处理器。
通过JVM参数-XX:+UseSerialGC可以使用串行垃圾回收器。
Serial与Serial Old工作过程如图:
要启用老年代串行收集器,可以尝试使用下面的参数:
并行收集器是多线程的收集器,在多核CPU下能够很好的提高收集性能。
ParNew收集器是Serial收集器的多线程版本。除了使用多条线程进行垃圾收集之外,其余行为如所有控制参数(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。ParNew收集器的工作过程如下图所示:
开启ParNew收集器可以使用以下参数:
ParNew是并行的收集器,在这里介绍一下并行与并发的概念
Parallel Scavenge收集器与ParNew收集器类似,也是使用复制算法的并行的多线程新生代收集器。但Parallel Scavenge收集器关注可控制的吞吐量(Throughput)
注:吞吐量是指CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /( 运行用户代码时间 + 垃圾收集时间 )
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量:
除上述两个参数之外,Parallel Scavenge收集器还提供了一个参数-XX:+UseAdaptiveSizePolicy,当这个参数打开之后,就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供的。
由于如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old(PS MarkSweep)收集器外别无选择(Parallel Scavenge无法与CMS收集器配合工作),Parallel Old收集器的出现就是为了解决这个问题。Parallel Scavenge和Parallel Old收集器的组合更适用于注重吞吐量以及CPU资源敏感的场合。Parallel Old收集器的工作过程下图所示:
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,也是基于“标记—清除”算法实现的,它的运作整个过程过程细分为4个步骤,包括:
由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行,如图:
CMS有以下3个明显的缺点:
CMS收集器对CPU资源非常敏感。CMS默认启动的回收线程数是( CPU Count + 3 ) / 4,当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并且随着CPU数量的增加而下降。但是当CPU不足4个(譬如2个)时,将分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了50%。
CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。浮动垃圾是指CMS在并发清理阶段用户线程还在同时执行时产生的垃圾。由于在垃圾收集阶段用户线程还需要运行,还需要预留有足够的内存空间给用户线程使用,因此需要预留一部分空间提供并发收集时的程序运作使用。在JDK 1.5的默认设置下,CMS收集器当老年代使用了68%的空间后就会被激活,可以适当调高参数-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以降低内存回收次数;在JDK 1.6中,CMS收集器的启动阈值已经提升至92%。如果运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将临时启用Serial Old收集器来重新进行老年代的垃圾收集,导致停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置得太高很容易导致大量“Concurrent Mode Failure”失败,性能反而降低。
CMS是一款基于“标记—清除”算法实现的收集器,收集结束时会有大量空间碎片产生。空间碎片过多无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数(默认就是开启的),用于在CMS收集器要进行Full GC时开启内存碎片的合并整理过程,内存整理的过程是无法并发的,因此也会导致停顿时间变长。而另外一个参数-XX:CMSFullGCsBeforeCompaction可以设置执行多少次不压缩的Full GC后,才执行一次带压缩的(默认值为0,即每次进入Full GC时都进行碎片整理)。
G1是一款面向服务端应用的垃圾收集器,与其他GC收集器相比,G1具备如下特点:
G1根据各个Region回收所获得的空间大小以及回收所需时间等指标在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region,从而可以有计划地避免在整个Java堆中进行全区域的垃圾收集。
G1收集器运作步骤如下:
参数 | 描述 |
---|---|
UseSerialGC | 虚拟机运行在Client模式下的默认值,打开此开关后,使用 Serial+Serial Old 的收集器组合进行内存回收 |
UseParNewGC | 打开此开关后,使用 ParNew + Serial Old 的收集器组合进行内存回收 |
UseConcMarkSweepGC | 打开此开关后,使用 ParNew + CMS + Serial Old 的收集器组合进行内存回收。Serial Old 收集器将作为 CMS 收集器出现 Concurrent Mode Failure 失败后的后备收集器使用 |
UseParallelGC | 虚拟机运行在 Server 模式下的默认值,打开此开关后,使用 Parallel Scavenge + Serial Old(PS MarkSweep) 的收集器组合进行内存回收 |
UseParallelOldGC | 打开此开关后,使用 Parallel Scavenge + Parallel Old 的收集器组合进行内存回收 |
SurvivorRatio | 新生代中 Eden 区域与 Survivor 区域的容量比值,默认为8,代表 Eden : Survivor = 8 : 1 |
PretenureSizeThreshold | 直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配 |
MaxTenuringThreshold | 晋升到老年代的对象年龄,每个对象在坚持过一次 Minor GC 之后,年龄就增加1,当超过这个参数值时就进入老年代 |
UseAdaptiveSizePolicy | 动态调整 Java 堆中各个区域的大小以及进入老年代的年龄 |
HandlePromotionFailure | 是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个 Eden 和 Survivor 区的所有对象都存活的极端情况 |
ParallelGCThreads | 设置并行GC时进行内存回收的线程数 |
GCTimeRatio | GC 时间占总时间的比率,默认值为99,即允许 1% 的GC时间,仅在使用 Parallel Scavenge 收集器生效 |
MaxGCPauseMillis | 设置 GC 的最大停顿时间,仅在使用 Parallel Scavenge 收集器时生效 |
CMSInitiatingOccupancyFraction | 设置 CMS 收集器在老年代空间被使用多少后触发垃圾收集,默认值为 68%,仅在使用 CMS 收集器时生效 |
UseCMSCompactAtFullCollection | 设置 CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理,仅在使用 CMS 收集器时生效 |
CMSFullGCsBeforeCompaction | 设置 CMS 收集器在进行若干次垃圾收集后再启动一次内存碎片整理,仅在使用 CMS 收集器时生效 |