在Java虚拟机中,GC回收器不仅仅只有一种,在哪一块内存区域以及场景使用什么垃圾回收器,对于我们编写的程序的性能都有着至关重要的影响,因为,如果我们熟悉每一个GC回收器的运行机制就可以给我们的程序的性能带来很大的提升,本篇文章中,我们来探究一下每一种GC回收器的结构原理。
首先,在JVM中,总共有以下几种GC回收器:
串行回收器是所有垃圾回收器中最古老的一种,也是最基本的垃圾回收器之一,串行回收器主要有以下两个特点:
-XX:+UseSerialGC
可以指定使用新生代串行收集器和老年代串行收集器,当虚拟机在Client模式下运行时,它是默认的垃圾收集器。它和上面的回收器一样,不同的是它使用在老年代中,使用标记-压缩算法,因此,在堆内存空间较大的应用程序中,一旦老年代串行收集器启动,应用程序很可能会因此停顿较长的时间。
它一般和其他的新生代回收器配合使用,同时它也作为CMS回收器的备用回收器,当CMS回收器回收不及时导致内存不够用的时候就会启用该回收器来回收。
如果要启用老年代串行回收器,可以尝试使用下面参数:
-XX:+UseSerialGC
:新生代,老年代都使用串行回收器。-XX:+UseParNewGC
:新生代使用ParNew回收器,老年代使用串行回收器。-XX:+UseParallelGC
:新生代使用ParallelGC回收器,老年代使用串行收集器。ParNew回收器是一个工作在新生代的垃圾回收器,它只是简单地将串行回收器多线程化,它的回收策略,算法以及参数和新生代串行回收器一样,只是多线程发挥了多核CPU的作用,提高了回收的效率,但是在单CPU下,反而多线程的切换的压力不如单线程性能好。
开启ParNew回收器可以使用下面的参数:
-XX:+UseParNewGC
:新生代使用ParNew回收器,老年代使用串行回收器。-XX:+UseConcMarkSweepGC
:新生代使用ParNew回收器,老年代使用CMS。ParNew回收器工作时的线程数量可以使用-XX:ParallelGCThreads
参数指定,一般,最好与CPU数量相当,避免过多的线程数,影响垃圾收集性能。在默认情况下,当CPU数量小于8个时,ParallelGCThread的值等于CPU数量,当CPU数量大于8个时,ParallelGCThreads的值等于3+(5*CPU_Count)/8。
新生代ParallelGC回收器也是使用复制算法的回收器,虽然它和ParNew回收器一样,是多线程,独占式的回收器,但是它有一个重要的特点:非常关注系统的吞吐量。
可以使用下面的参数来启用该回收器:
-XX:+UseParallelGC
:新生代使用ParallelGC回收器,老年代使用串行回收器。-XX:+UseParallelOldGC
:新生代使用ParallelGC回收器,老年代使用ParallelOldGC回收器。ParallelGC回收器有两个重要的参数来控制系统的吞吐量:
-XX:MaxGCPauseMillis
:设置最大停顿时间。它的值是一个大于0的整数,ParallelGC在工作时,会调整Java堆大小或者其他一些参数,尽可能地把停顿时间控制在该参数以内。如果读者希望减少停顿时间,而把这个值设的很小,为了达到预期的停顿时间,虚拟机可能会使用一个较小的堆,而这种导致垃圾回收变得很频繁,从而增加了垃圾回收总时间,降低了吞吐量。-XX:GCTimeRatio
:设置吞吐量大小,它的值是一个0到100之间的整数,假设GCTimeRatio的值为n,那么系统将花费不超过1/(1+n)的时间用于垃圾收集。除了关注系统的吞吐量,ParallelGC回收器与ParNew回收器另一个不同之处在于它还支持一种自适应的GC调节策略,使用-XX:+UseAdaptiveSizePolicy
可以打开自适应GC策略。
它和新生代ParallelGC回收器一样,它也是一种关注吞吐量的回收器,不过它使用在老年代中,使用标记-压缩算法,参数-XX:ParallelGCThreads
用于设置垃圾回收时的线程数量。
与ParallelGC和ParallelOldGC不同,CMS回收器主要关注于系统停顿时间,CMS是Concurrent Mark Sweep的缩写,意为并发标记清除,所以它使用标记-清除算法,是一个使用多线程并行回收的垃圾回收器。
CMS的工作过程分为:初始标记,并发标记,预清理,重新标记,并发清除和并发重置。其中初始标记和重新标记是独占系统资源的,而预清理,并发标记,并发清理和并发重置是可以和用户线程一起执行的。因此,从整体上来说,CMS回收器不是独占式的,它可以在应用程序运行过程中进行垃圾回收。
初始标记,并发标记,重新标记都是为了标记出需要回收的对象,并发清理则是在标记完成后,正式回收垃圾对象,并发重置是指在垃圾回收完成后,重新初始化CMS数据结构和数据,为下一次垃圾回收做好准备。并发标记、并发清理和并发重置都是可以和应用程序线程一起执行的。
在整个CMS回收的过程中,默认情况下,在并发标记之后,会有一个预清理的操作,可以通过开关-XX:-CMSPrecleaningEnabled
关闭,不进行预清理。由于重新标记是独占CPU的,如果在一次新生代GC过后,立即触发一次重新标记,那么一次停顿时间可能会很长,为了避免这种情况,预处理时,会刻意等待一次新生代GC的发生,然后根据历史性能数据预测下一次新生代GC可能发生的时间,然后在当前时间和预测时间的中间时刻,进行重新标记,这样,从最大程度上避免新生代GC和重新标记重合,尽可能减少一次停顿时间。
可以使用参数-XX:+UseConcMarkSweepGC
来启用该回收器。由于CMS回收器不是独占式的回收器,在CMS回收过程中,应用程序任然在不停地工作,在应用程序工作过程中,又不断地产生垃圾。这些新生成的垃圾在当前CMS回收过程中是无法清除的,同时,因为应用程序没有中断,所以在CMS回收过程中,还应该确保应用程序有足够的内存可用。因此CMS回收器不会等待堆内存饱和时才进行垃圾回收,而是当堆内存使用率达到某一个阀值时便开始进行回收,以确保应用程序在CMS工作过程中,依然有足够的空间支持应用程序运行。
这个回收阀值可以使用-XX:CMSInitiatingOccupancyFraction
来指定,默认是68,即当老年代空间使用率达到68%时,会执行一次CMS回收,如果应用程序的内存使用率增长很快,在CMS的执行过程中,已经出现了内存不足的情况,此时,CMS回收就会失败,虚拟机将启动老年代串行收集器进行垃圾回收。
由于CMS是使用标记-清除算法,因此会产生内存碎片,-XX:+UseCMSCompactAtFullCollection
开关可以使CMS在垃圾收集完成后,进行一次内存碎片整理,内存碎片的整理不是并发进行的。-XX:CMSFullGCBeforeCompaction
参数可以用于设定进行多少次CMS回收后,进行一次内存压缩。
G1回收器是JDK1.7中正式使用的全新的垃圾回收器,从长期目标来看,它是为了取代CMS回收器,G1回收器拥有独特的垃圾回收策略,这和之前提到的回收器截然不同,从分代来看,它任然属于分代垃圾回收器,它区分年轻代和老年代,但是它使用了分区算法,其特点如下:
G1的回收过程主要分为下面4个阶段:
G1的新生代GC和其他回收器一样,使用复制算法,当Eden区满的时候,就会触发年轻代GC,将存活对象移动到survivor区,算法和复制算法基本是一样的。
G1的并发标记周期
并发标记周期可以分为以下几步:
在并发标记之后,系统的一些区域会被标记为G的区域,是因为它们内部的垃圾比例较高,因此希望在后续的混合GC中进行收集,这些将要被回收的区域会被G1记录在一个称为Collection Sets(回收集)的集合中。
在混合回收阶段,它会优先回收一些垃圾比例高的区域,这也是G1回收器名字的由来(Garbage frist),混合回收,即同时处理了新生代和老年代。
和CMS类似,并发收集是与应用程序和GC线程交替工作,因此总不能完全避免在特别繁忙的场合会出现在回收过程中内存不充足的情况。当遇到这种情况时,G1也会转入一个Full GC进行回收。此外,如果在混合GC时发生空间不足或者在新生代GC时,survivor区和老年代无法容纳幸存对象,都会导致一次full gc产生。
G1相关参数
可以使用-XX:+UseG1GC
标记打开G1收集器开关,对G1收集器进行设置时,最重要的一个参数是-XX:MaxGCPauseMills
,它用于指定目标最大停顿时间,如果任何一次停顿超过这个设置值时,G1就会尝试调整新生代和老年代的比例,调整堆大小,调整晋升年龄等手段,试图达到预设目标。
还有一个参数是-XX:ParallelGCThreads
,它用来设置并行回收时,GC的工作线程数量。
参数-XX:InitiatingHeapOccupancyPercent
参数可以指定当整个堆使用率达到多少时,触发并发标记周期的执行,默认值是45,即当整个堆的占用率达到45%时,执行并发标记周期,这个值一旦设定,就不会被G1所修改,如果设置的过大,那么就会导致并发标记迟迟不能执行,引起Full GC的可能性也大大增加,反之,一个过小的值,使得并发周期非常频繁,大量GC线程抢占CPU,会导致应用程序的性能有所下降。
本篇文章就介绍到这里,如有问题欢迎留言指正!!!
参考资料:《实战Java虚拟机-JVM故障诊断与性能优化》