CMS概述
并发标记清理垃圾回收(Concurrent Mark and Sweep GC)算法的主要目标是在GC过程中,减少暂停用户线程的次数以及在不得不暂停用户线程的请夸功能,尽可能短的暂停用户线程的时间。这对于交互式应用,比如web应用来说,是非常重要的。
CMS垃圾回收针对新生代和老年代采用不同的策略。相比同吞吐量垃圾回收,它要复杂的多。吞吐量垃圾回收在执行时必须暂停用户线程,并且暂停的时间可能比较长,吞吐量垃圾回收在运行时,完全无视了应用线程。不过这么做也有个好处,就是确保了GC线程在运行时不会跟用户线程发生线程互斥等线程同步问题,保证了用户数据。
但是,CMS垃圾回收的目标是要尽可能少的暂停用户线程,不得不暂停时,暂停用户线程的时间也要尽可能的短暂,它采用GC线程和用户线程同时工作的方式来保证用户系统的活跃性。凡事有利必有弊,这种方式的弊端在于可能潜在的引入GC和用户线程同步问题、数据一致性问题。
为了解决GC线程和用户线程的安全正确的同时并发,CMS垃圾回收包含如下几个GC回收阶段。
CMS垃圾回收的阶段
一次完整的CMS垃圾回收分为6个阶段。其中四个阶段是GC线程跟用户线程并发执行的,有两个阶段是JVM暂停用户线程,仅仅GC线程运行的。
- Initial Mark: The application threads are paused in order to collect their object references. When this is finished, the application threads are started again.
- Concurrent Mark: Starting from the object references collected in phase 1, all other referenced objects are traversed.
- Concurrent Preclean: Changes to object references made by the application threads while phase 2 was running are used to update the results from phase 2.
- Remark: As phase 3 is concurrent as well, further changes to object references may have happened. Therefore, the application threads are stopped once more to take any such updates into account and ensure a correct view of referenced objects before the actual cleaning takes place. This step is essential because it must be avoided to collect any objects that are still referenced.
- Concurrent Sweep: All objects that are not referenced anymore get removed from the heap.
- Concurrent Reset: The collector does some housekeeping work so that there is a clean state when the next GC cycle starts.
因此,CMS GC并非不暂停用户线程,只是暂停的时间比较短。上面的步骤是发生在老年代堆的垃圾回收,对于新生代,新生代仍然采用完全暂停用户线程的做法,这是因为新生代的垃圾回收通常非常快,都是朝生夕死的对象
CMS垃圾回收的问题
CMS垃圾回收有两个问题:
a.堆的碎片问题
b.频繁对象空间分配
1. 堆的碎片问题
不同于吞吐量垃圾回收,CMS垃圾回收没有提供内存碎片的整理功能,因此,极坏的情况下老年代堆空间有可能出现大量的内存空间碎片,即使老年代堆空间足够大,也有可能因为找不到连续的内存空间而导致内存分配失败而引起全量GC,全量GC(Full GC)采用的算法跟吞吐量GC一样,能够解决碎片问题,但是会暂停用户线程。
2.频繁对象空间分配
这里的频繁对象空间分配是指创建对象的操作远远超过删除对象的操作(比如,往一个集合中放30万个对象),这时,如果达到新生代堆容量上限,会将位于新生代的还在集合中的对象
移动到老年代,如果老年代有较多碎片,频繁的将对象从新生代移动到老年代,也会触发Full GC,
解决上面两个问题的做法是,如果使用CMS垃圾回收,那么需要将新生代的空间分配大一点,避免出现新生代对象向老年代移动的情况。
与CMS相关的JVM参数
1.-XX:+UseConcMarkSweepGC
指示JVM使用CMS GC,而不是默认的ThroughOutput GC
2.-XX:+UseParNewGC
当使用CMS GC时,这个参数指示JVM对新生代堆空间采用多线程并行的方式进行GC。这个参数乍看上去有点多余,因为Throughput GC已经有一个称为-XX:+UseParallelGC的JVM
参数,并且CMS对于新生代的垃圾回收方式与Througput GC的做法一样。那为什么对于CMS垃圾回收,CMS专门定义-XX:UseParNewGC
这个参数呢?因为对于CMS垃圾回收,对于新生代和老年代的垃圾回收,采用了两种不同的算法,因此对于新生代使用-XX:UseParNewGC
,而对于老年代则沿用Throughput GC一样的参数-XX:+UseParallelGC。
需要注意的是,-XX:+UseConcMarkSweepGC启用后,
-XX:UseParNewGC将默认启用,因此如果不想使用
-XX:UseParNewGC这个参数,则需要使用
-XX:-UseParNewGC
3.-XX:+CMSConcurrentMTEnabled
使用这个参数指示JVM,在CMS垃圾回收的并发阶段(不是暂停用户线程的阶段),使用多个GC多线程参与GC,这些GC线程与用户线程并发执行,这个参数默认是开启的
4.-XX:ConcGCThreads
这个参数用来设置执行CMS并发收集(不暂停用户线程,第三个参数启用)的线程数。默认情况下,JVM给定的默认值是ThroughPut垃圾回收的并发数-XX:ParallelGCThreads,
(-XX:ParallelGCThreads + 3)/4。因此,使用
-XX:ParallelGCThreads不仅影响暂停用户阶段的线程数,还影响了并发垃圾回收的并发数。是否可以了解决为,暂停用户线程的GC阶段,使用的线程数是由
-XX:ParallelGCThreads控制的?
5.-XX:CMSInitiatingOccupancyFraction
当堆满的时候(比如没有足够的空间容纳新创建的对象或者没有足够的空间容纳从新生代迁移到老年代的对象),Throughput垃圾回收器会启动一轮GC。但是对于CMS垃圾回收来讲,不应该等到堆满的时候再来执行垃圾回收,因为垃圾回收器执行的同时,应用程序也在执行,此时容易导致内存溢出的问题(应用创建对象的速度大于垃圾回收的速度)。因此,对于CMS垃圾回收器来说,它的回收时机应该提前而不能等到内存即将用完的情况下。
JVM使用-XX:CMSInitiatingOccupancyFraction=<value>指示CMS垃圾回收器开始垃圾回收第一阶段,其中的value指的是老年代的内存使用率。比如
-XX:CMSInitiatingOccupancyFraction=75%,表示当老年代的内存使用率超过75%时,则启动第一阶段的垃圾回收。这个参数的默认值是68,应该是取2/3之意。
6.-XX:+CMSClassUnloadingEnabled
不同于Throughput垃圾回收器,CMS垃圾回收器默认情况下不执行永久代的垃圾回收。如果要对永久代进行垃圾回收,则可以使用-XX:+CMSClassUnloadingEnabled启用永久代垃圾回收。注意:即使没有设置这个选项,当永久代没有可用空间时,GC也会对永久代进行垃圾回收,这个回收的过程是Full GC,因此它不是并发的,也会暂停用户线程
7. -XX:+CMSIncrementalMode
这个选项启用了CMS垃圾回收器的增量模式。增量模式暂停CMS的并发阶段(即GC线程和用户线程同时工作的阶段)以让路给用户线程。因此,CMS垃圾回收器执行完所有的GC阶段将要使用更长的时间。使用这个参数只有在确定的知道CMS GC线程阻扰了用户线程的执行,否则不要设置这个选项,此外,现代的多核服务器完全能够容纳GC和用户线程同时并发
8. -XX:+ExplicitGCInvokesConcurrent and -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
通常,应用通过调用System.gc()来强制进行Full GC是违反最佳实践的做法,这个不论采用使用的垃圾回收算法,显式调用System.gc都是违反最佳实践的做法。这对于CMS垃圾回收来说尤其糟糕,因为System.gc()默认是启动Full GC,这跟CMS GC过程中尽量缩短用户线程的等待时间背道而驰。所幸,JVM提供几个选项来改变这个行为。 -XX:+ExplicitGCInvokesConcurrent选项指示JVM当System GC请求垃圾回收时,JVM应该启动CMS GC而不是默认的Full GC。
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses选项指示JVM在执行System请求的垃圾回收时,一方面执行CMS GC,另一方面也执行永久代的垃圾回收。
9.-XX:+DisableExplicitGC
这个选项用于指示JVM不管垃圾回收采用什么算法,都忽略System.gc()发起的垃圾回收请求。
本文参考https://blog.codecentric.de/en/2013/10/useful-jvm-flags-part-7-cms-collector/