CMS执行垃圾回收分为4个阶段
1)初始标记
标记出来所有GC Root直接引用的对象。会造成STW,但影响不大,因为速度快。
2)并发标记
垃圾回收和工作线程并行,尽可能的对所有的对象进行Root追踪。最耗时的阶段,但因为是并行的,不会对系统造成影响。
3)重新标记
重新标记第二阶段里新创建的一些对象,还有一些已有对象可能失去引用变为垃圾。STW,但速度快,因为只是标记系统运行第二阶段的对象。
4)并发清理
垃圾回收和工作线程并行,虽然很耗时,但不影响系统运行。
CMS后的碎片整理
CMS有个参数-XX:+UseCMSCompactAtFullCollection,默认就打开了,意思是在Full GC后再次STW,进行碎片整理。
还有个参数-XX:+CMSFullGCsBeforeCompaction,表示执行多少次Full GC后再执行一次内存碎片整理的工作,默认是0,意思是每次Full GC后都会进行一次内存整理。
为何Full GC要比Minor GC慢很多
1)新生代存活对象很少,从GC Roots出发不需要追踪多少对象,速度是很快的。然后直接把存活对象放入Survivor,一次性直接回收Eden和一块Survivor。
2)老年代存活对象多,并发标记要追踪所有存活对象,慢。并发清理,不是一次性回收一大片内存,而是找到零散的垃圾对象,慢。然后还得碎片整理,STW,慢。万一并发清理期间,剩余内存空间不足以存放新进入老年代的对象,发生Concurrent Model Failure,还得用“Serial Old”垃圾回收器,STW,重新来一遍回收过程,慢。
CMS并发机制的缺点
1)消耗CPU资源
2)并发清理阶段,可能还会放入新对象,这时会产生“浮动垃圾”。
3)如果在回收期间,系统程序放入的新对象大于老年代可用空间,会发生Concurrent Model Failure。此时就会自动用“Serial Old”垃圾回收器代替CMS,STW,重新进行长时间的GC Roots追踪,标记全部垃圾对象并一次性回收,再回复工作线程。
垃圾回收优化
1)Survivor空间要够,避免新生代GC后存活对象放不下进入老年代;或动态年龄超过50%。
2)让长期存活的对象尽快进入老年代,可以是情况降低默认15的年龄。
总结
1)parNew默认的回收线程数量跟CPU的核数一样。比如,4核,就会启动4个线程。这样每个线程都通过一个CPU在运行。但也可以通过-XX:+ParallelGCThreads参数来调节,但建议默认就行。
2)CMS默认启动的线程数是(CPU核数 + 3)/ 4
3)Full GC优化的前提是Minor GC的优化,Minor GC的优化的前提是合理分配内存空间,合理分配内存空间的前提是对系统运行期间的内存使用模型进行预估。
4)其实,合理分配好内存空间,尽量让Minor GC后的存活对象留在Survivor里不要去老年代,就差不多了。