一、新生代垃圾回收器的比较:
二、老年代垃圾回收器的比较:
三、CMS垃圾收集器
1、CMS(Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器,适用于集中在互联网站或者
B/S系统的服务端的Java应用。
2、CMS收集器是基于"标记-清除"算法实现的,可以跟新生代的parallel New、Serial搭配使用。
四、CMS的特性
1、CMS只会回收老年代和永久代的垃圾,不会收集年轻代;
2、CMS是一种预处理垃圾回收器,它不能等到old内存用尽时回收,需要在内存用尽之前,完成回收操作,否则会导致并发回收失败,所以CMS垃圾回收器开始执行回收操作,有一个触发阈值,JDK6之前的默认阈值是68%, 而JDK6及以上版本是92%;
3、CMS不会移动对象以保证空闲空间的连续性,相反,CMS保存所有空闲内存片段的列表,通过这样的方式,CMS可以避免为存活对象重新分配位置引起的开销,但是相应的会引起内存的碎片化。
五、CMS的执行步骤
1、初始标记:
标记老年代中所有的GC Roots对象;
标记年轻代中活着的对象引用到的老年代对象(指的是年轻代中还存活的引用类型对象,引用指向老年代中的对象)
2、并发标记
因为是并发运行的,在运行期间会发生新生代的对象晋升到老年代、或者是直接在老年代分配对象、或者更新老年代对象的引用关系等等,对于这些对象,都是需要进行重新标记的,否则有些对象就会被遗漏,发生漏标的情况。为了提高重新标记的效率,该阶段会把上述对象所在的Card标识为Dirty,后续只需扫描这些Dirty Card的对象,避免扫描整个老年代。
3、预清理阶段
前一个阶段已经说明,不能标记出老年代全部的存活的对象,是因为标记的同时应用程序会改变一些对象引用,这个阶段就是用来处理前一个阶段因为引用关系改变导致没有标记到的存活对象,它会扫描所有标记的Dirty Card。
4、重新标记
这个阶段会导致第二次stop the word,该阶段的任务是完成标记整个老年代的所有存活对象。
这个阶段,重新标记的内存范围是整个堆,包含 young_gen和old_gen。为什么要扫描新生代呢,因为对于老年代中的对象,如果被新生代中的对象引用,那么就会被视为存活对象,即使新生代的对象已经不可达了,也会使用这些不可达的对象当做CMS的"GC root"来扫描老年代;因此对于老年代来说,引用了老年代中对象的新生代对象,也会被老年代视为"GC roots", 当此阶段耗时较长的时候,可以加入参数 -XX:+CMSScavengeBeforeRemark,在重新标记之前,先执行一次ygc,回收掉年轻代的无用对象,并将对象放入幸存代或者晋升到老年代,这样再进行年轻代扫描时,只需要扫描幸存区的对象即可,一般幸存代非常小,这大大减少了扫描时间。
5、并发清理
通过以上5个阶段的标记,老年代所有存活的对象已经被标记并且现在要通过Garbage Collector 采用清扫的方式回收那些不能用的对象。
这个阶段只要是清除那些没有标记的对象并且回收空间;
由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留下一次GC时再清理掉,这一部分就称为"浮动垃圾"。
6、并发重置
这个阶段并发执行,重新设置CMS算法内部的数据结构,准备下一个CMS生命周期的使用。
六、优化
1、一般CMS的GC耗时80%都在remark阶段,如果发现remark阶段停顿时间很长,可以尝试添加该参数: -XX:+CMSScavengeBeforeRemark
2、CMS是基于标记-清除算法的,只会将标记为不存活的对象删除,并不会移动对象整理内存空间,会造成内存碎片,这时我们需要用到这个参数:
-XX:+CMSFullGCsBeforeCompaction=n
●CMS GC要决定是否在full GC时做压缩,会依赖几个条件,其中:
①、UseCMSCompactAtFullCollection 与 CMSFullGCsBeforeCompaction 是搭配使用的,前者目前默认就是true了,也就是关键在后者。
②、用户调用了System.gc(),而且DisableExplicitGC没有开启。
③、young gen报告接下来如果做增量收集会失败,简单来说也就是young gen预计 old gen 没有足够空间来容纳下次young GC晋升的对象。
●上述三种条件的任意一种成立都会让CMS决定这次做full GC时要做压缩。
3、执行CMS GC的过程中,同时业务线程也在运行,当年轻代空间满了,执行ygc时,需要将存活的对象放入到老年代,而此时老年代空间不足,这时CMS还没有机会回收老年代产生的,或者在做Minor GC的时候,新生代空间放不下,需要放入老年代,而老年代也放不下而产生concurrent mode failure.
要确定发生concurrent mode failure 的原因是因为碎片造成的,还是Eden区有大对象直接晋升老年代造成的,一般有大量的对象晋升老年代容易导致这个错,这种是存在优化空间的,要保证大部分对象尽可能的再新生代gc 掉。
4、CMS默认启动的回收线程数目是(ParallelGCThreads + 3) / 4, 这里的ParallelGCThreads是年轻代的并行收集线程数;
年轻代的并行收集线程数默认是(ncpus <= 8) ? ncpus : 3 + ((ncpus * 5) / 8),可以通过
-XX:ParallelGCThreads = N 来调整;如果要直接设定CMS回收线程数,可以通过:
-XX:ParallelCMSThreads = n,注意这个n不能超过cpu线程数,需要注意的是增加gc线程数,就会和应用争抢资源。