JVM之垃圾回收-垃圾收集器( 三 )


所以总体来说,CMS的内存回收是与用户线程一起“并发”执行的 。
CMS收集器运行示意图如下:
设置参数
指定使用CMS收集器"-XX:+UseConcMarkSweepGC"
缺点 (一)对CPU资源敏感
面向并发设计的程序都对CPU资源比较敏感(并发程序的特点) 。在并发阶段,它虽然不会导致用户线程停顿,但会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低 。(在对账系统中,不适合使用CMS收集器) 。
CMS的默认收集线程数量是=(CPU数量+3)/4; 当CPU数量越多,回收的线程占用CPU就少 。
也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,对用户程序影响可能较大;不足4个时,影响更大,可能无法接受 。(比如 CPU=2时,那么就启动一个线程回收,占了50%的CPU资源 。)
(一个回收线程会在回收期间一直占用CPU资源)
针对这种情况,曾出现了"增量式并发收集器"(Mark Sweep/i-CMS);
类似使用抢占式来模拟多任务机制的思想,让收集线程和用户线程交替运行,减少收集线程运行时间;
但效果并不理想,JDK1.6后就官方不再提倡用户使用 。
(二)无法处理浮动垃圾
无法处理浮动垃圾,可能出现" Mode "失败
在并发清除时,用户线程新产生的垃圾,称为浮动垃圾;
解决办法
这使得并发清除时需要预留一定的内存空间,不能像其他收集器在老年代几乎填满再进行收集;
也可以认为CMS所需要的空间比其他垃圾收集器大; 可以使用"-XX:",设置CMS预留老年代内存空间; (详解见名词解释)
(三)产生大量内存碎片
由于CMS是基于“标记+清除”算法来回收老年代对象的,因此长时间运行后会产生大量的空间碎片问题,可能导致新生代对象晋升到老生代失败 。
由于碎片过多,将会给大对象的分配带来麻烦 。因此会出现这样的情况,老年代还有很多剩余的空间,但是找不到连续的空间来分配当前对象,这样不得不提前触发一次Full GC 。
解决办法
使用"-XX:+"和"-XX:+",需要结合使用 。
"-XX:+UseCMSCompactAtFullCollection"
为了解决空间碎片问题,CMS收集器提供?XX:+标志,使得CMS出现上面这种情况时不进行Full GC,而开启内存碎片的合并整理过程;
由于合并整理是无法并发执行的,空间碎片问题没有了,但是有导致了连续的停顿 。因此,可以使用另一个参数?XX:,表示在多少次不压缩的Full GC之后,对空间碎片进行压缩整理 。
可以减少合并整理过程的停顿时间;
默认为0,也就是说每次都执行Full GC,不会进行压缩整理;
由于空间不再连续,CMS需要使用可用"空闲列表"内存分配方式,这比简单实用"碰撞指针"分配内存消耗大;
CMS& Old
总体来看,CMS与 Old垃圾收集器相比,CMS减少了执行老年代垃圾收集时应用暂停的时间;
但却增加了新生代垃圾收集时应用暂停的时间、降低了吞吐量而且需要占用更大的堆空间;
(原因:CMS不进行内存空间整理节省了时间,但是可用空间不再是连续的了,垃圾收集也不能简单的使用指针指向下一次可用来为对象分配内存的地址了 。相反,这种情况下,需要使用可用空间列表 。即,会创建一个指向未分配区域的列表,每次为对象分配内存时,会从列表中找到一个合适大小的内存区域来为新对象分配内存 。这样做的结果是,老年代上的内存的分配比简单实用碰撞指针分配内存消耗大 。这也会增加年轻代垃圾收集的额外负担,因为老年代中的大部分对象是在新生代垃圾收集的时候从新生代提升为老年代的 。)