在JAVA虚拟机中垃圾回器有好几种,那么在什么情况下使用哪一种,对应用程序有哪些影响,我们需要了解每种垃圾回收器的特性和使用方法,垃圾回收器主要是几下几种,分别是串行垃圾回收器,并行垃圾回收器、CMS垃圾回收器、G1垃圾回收器以及JDK14推出的ZGC垃圾回收器。
串行垃圾回收器是JDK中最基本也是最古老的垃圾回收器,它使用单线程进行垃圾回收,每次回收时,串行回收器只有一个工作线程,对于性能较弱的计算来说,串行回收器效果更好,因为只有一个线程且是独占方式,无需线程切换,性能表现还是非常不错的,该回收器有两个主要特点:
- 1、它仅仅使用单线程进行垃圾回收;
- 2、它是独占式的垃圾回收方式,因此会出现STW现像;
串行垃圾回收器使用-XX:+UseSerialGC参数进行指定,这个参数是指新生代和老年代都使用串行垃圾回收,也是虚拟机默认的垃圾回收器,JDK1.9之前版本默认串行垃圾回收器,而JDK1.9及后续版本默认是G1垃圾回收器)。
对老年代串行垃圾回收器使用的是标记清除和标记压缩算法,和新生代一样它是串行独占式的,所以会出现STW,由于老年代在堆的空间中占比较大,一旦FULLGC启动,应用程序停顿的时间较长。老年代串行垃圾回收器可以和多种新生代回收器配合使用,同时它可以作为CMS收器的备用回收器,若要启动老年代串行回收器,可以使用以下参数进行设置: - -XX:+UseSerialGC: 新生代和老年代都使用串行垃圾回收器
- -XX:+UseParNewGC:新生代使用ParNew回收器,老年代则使用串行垃圾回收器
- -XX:+UseParalleGC:新生代使用ParalleGC回收器,老年代使用串行回收器。
-
三、并行回收器
随着计算机快速进步,现在线上服务器都是24CPU、32G内存已非常常见,而垃圾回收器也是与时俱进,并行回收器使用多个线程进行垃圾回收,对于计算机能力较强的机器,可以有效减少垃圾回收所需的实际时间。
parNew回收器是一个工作在新生代的垃圾回收器,它只是简单地将串行回收器多线程化,它的回收策略、算法以及参数和新生代回收器一样,parNew回收器也是独占式的回收器,在回收过程中应用程序会全部暂停,在计算机能力较弱的机器上,它的回收器效果不一定比串行回收器效果好。
开启parNew回收器使用参数如下:
- -XX:+UseParNewGC:新生代使用ParNew回收器,老年代则使用串行回收器
- -XX:+UseConcMarkSweepGC :新生代使用ParNew回收器,老年代使用CMS回收器
ParNew回收工作时线程数量可以使用-xx:ParallelGCThreads参数指定,一般最好与CPU数量相当,避免过多的线程数影响垃圾回收性能,在默认情况下当CPU数量小8时,ParallelGCThreads的值等于CPU数量,当CPU数量大于8时,ParallelGCThread的值等于3+((5*CPU)/8)。 -
3.1.2 新生代ParallelGC回收器
新生代parallelGC回收器也是使用复制算法的回收器,它与parNew一样,都是多线程独占式的回收器,但parallelGC有一个非常重要的特点,它非常关注系统的吞吐量,它支持自适应的GC调节策略,使用-XX:UseAdaptiveSizePolicy可以打开自适应GC策略,在这种模式下,新生代eden、servivor区的比例、晋升老年代的对象年龄等参数会被自动调整,它会在吞吐量、堆的大小以及停顿时间上做一个平衡,此种模式适合手工调优比较困难的场合。
参数使用如下:
- -XX:UseParallelGC:新生代使用parallelGC回收器,老年代使用串行回收器。
- -XX:UseParallelOldGC:新生代使用parallelGC回收器,老年代使用parallelOldGC回收器
- -XX:MaxGCPauseMillis:设置最大垃圾回收停顿时间
- -XX:GCTimeRatio:设置吞吐量大小,它的值是一个0到100之间整数,默认值是19,也就是说垃圾回收的时间不超过1/(1+19)的时间。
parallelGC 回收器关注系统吞吐量,可以通过-XX:MaxGCPauseMillis和-XX:GCTimeRatio设置期望的停顿时间和吞吐量,但这两个参数是相互矛盾的,增加系统吞吐量又可能增加一次垃圾回收的最大停顿的最大时间。 -
3.1.2 老年代ParallelOldGC回收器
老年代parallelOldGc回收器是在JDK1.6引入,也是一种多线程并发回收器,它也是一种非常关注系统吞吐量的回收器,它主要和parallelGc 新生代回收器一起搭配使用,设置参数与新生代一致。
在JDK1.5时期HotSpot推出了一款在强交互应用中几乎可认为有划 时代意义的垃圾收集器: CMS (Concurrent 一Mark 一 Sweep)收集器,这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作,它与parallelGC、parallelOLdGC不同之处是CMS更加关注系统停顿时间,CMS工作时主要步聚有初始标记、并发标记、预清理、并发标记、并发清除和并发重置,其中初始标记和重新标记是独占资源的,而预清理、并发标记和并发重置是可以和用户线程一起执行的,CMS流程图如下:
根据标记清除算法,初始标记、并发标记以及重新标记都是为了标记出需要回收的对像,而并发清理则是在标记完成后,正式回收垃圾对象,并发重置是指在垃圾回收完成后,重新初始化CMS的数据结构和数据,为下一次垃圾回收做好准备。
-
4.1.1 CMS关键点说明
- 1、初始标记(Initial一Mark) 阶段:STW||在这个阶段中,程序中所有的工作线程都将会因为 “Stop一the一World"机制而出现短暂的暂停,这个阶段的主要任务仅仅只是标记出GCRoots能直接关联到的对象,一旦标记完成之后就会恢复之前被暂停的所有应用线程,由于直接关联对象比较小,所以这里的速度非常快。
- 2、并发标记(Concurrent一Mark)阶段:从GC Roots的 直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。
- 3、预清理:默认情况下CMS回收过程在并发标记后会有一个预清理的操作(可以通过关闭开关-XX:-CMSPrecleaningEnabled),预清理它是并发进行的,主要为正式清理做检查和准备,它会尝试控制一次停顿的时间,由于重新标记它是独占CPU,如果新生代GC发生后会立刻触发一次重新标记,那么一次停顿的时间较长,为了避免这种情况,预清理会刻意等一次新生代GC,然后根据历史性能数据预测下一次新生代GC可能发生的时间,在当前时间和预测时间的中间点进行重新标记,这个尽量避免新生代GC和重新标记重合,减少停顿的时间。
- 4、重新标记(Remark) 阶段:STW||由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短。
- 5 、并发清除( Concurrent一Sweep)阶段:此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的
-
4.1.2 CMS特点分析
- 1、由于最耗费时间的并发标记与并发清除阶段都不需要暂停工作,所以整体的回收是低停顿的。
- 2、尽管CMS收集器采用的是并发回收(非独占式),但是在其初始化标记和重新标记这两个阶段中仍然需要执行“Stop一the一World”机制暂停程序中的工作线程,不过暂停时间并不会太长。
- 3、因此可以说明目前所有的垃圾收集器都做不到完全不需要“Stop一the一World”,只是尽可能地缩短暂停时间。
- 4、另外,由于在垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序用户线程有足够的内存可用。因此,CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在CMS工作过程中依然有足够的空间支持应用程序运行。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial 0ld收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。
- 5、CMS收集器的垃圾收集算法采用的是标记一清除算法,这意味着每次执行完内存回收后,由于被执行内存回收的无用对象所占用的内存空间极有可能是不连续的一些内存块,不可避免地将会产生一些内存碎片。
那么CMS在为新对象分配内存空间时,将无法使用指针碰撞(Bump the Pointer) 技术,而只能够选择空闲列表(Free List) 执行内存分配。 -
CMS优缺点
- 1、优点:并发收集低延迟
- 2、CMS弊端:
1)会产生内存碎片,导致并发清除后,用户线程可用的空间不足。在无法分配大对象的情况下,不得不提前触发Full GC。
2) CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
3) CMS收集器无法处理浮动垃圾。可能出现“Concurrent Mode Failure" 失败而导致另一次Full GC的产生。浮动垃圾:在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或者交叉运行的,那么在并发标记阶段如果产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行GC时释放这些之前未被回收的内存空间。 -
CMS主要参数
-XX:+UseConcMarkSweepGc 手动指定使用CMS收集器执行内存回收任务。开启该参数后会自动将一XX:+UseParNewGc打开。即: ParNew (Young区用) +CMS (0ld区用) +Serial 0ld的组合。
-XX:CMS1ni tiatingOccupanyFraction设置堆内存使用率的阈值,一旦达到该阈值,便开始进行回收。JDK5及以前版本的默认值为68,即当老年代的空间使用率达到68时,会执行一 次CMS 回收,JDK6 5及以上版本默认值为92号,如果内存增长缓慢,则可以设置一个稍大的值,大的阈值可以有效降低CMS的触发频率,减少老年代回收的次数可以较为明显地改善应用程序性能。反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁触发老年代串行收集器。因此通过该选项便可以有效降低Full GC的执行次数。
-XX: +UseCMSCompactAtFullCollection用于指定在执行完Full GC后对内存空间进行压缩整理,以此避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变得更长了。
-XX:CMSFullGCsBeforeCompaction设置在执行多少次Full GC后对内存空间进行压缩整理。
-XX:ParallelCMSThreads 设置CMS的线程数量。
CMS 默认启动的线程数是(ParallelGCThreads+3) /4, ParallelGCThreads是年轻代并行收集器的线程数。当CPU资源比较紧张时,受到CMs收集器线程的影响,应用程序的性能在垃圾回收阶段可能会非常糟糕。
G1回收器是在JDK1.7中正式使用的全新垃圾回收器,从长期来看它是为了取代CMS回收器,G1回收器拥有独特的垃圾回收策略,从分代上来说还是划分两个逻辑区域新生代和老年代,但从堆的结构上来说并不要求新生代和老年代都连续,他使用的是分区算法,G1回收过程主要分为4个阶段,分别是新生代GC、并发标记周期、混合回收等几个阶段,具体见:https://www.jianshu.com/p/61fc29674324
ZGC 是一个低延迟的 GC 算法,最大暂停时间不超过 10 ms,并且暂停时间不会随着管理的堆内存增大而增大,它是在JDK15正式发布,该回收器跟MYSQL一样,引入了page的概念,它会对PAGE进行管理,由于比较新,还没有具体研究。