本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
ParNew和CMS垃圾回收器: ParNew现在一般都是用在新生代的垃圾回收器,CMS是用在老年代的垃圾回收器,他们都是多线程并发的机制,性能更好,现在一般是线上生产系统的标配组合。下周会着重分析这两个垃圾回收器。
ParNew用的就是复制算法。
老年代我们选择的垃圾回收器是CMS,他采用的是标记清理算法,其实非常简单,就是先用之前文章里讲过的标记方法去标记出哪些对象是垃圾对象,然后就把这些垃圾对象清理掉。但是这些操作是STW,在垃圾回收期间,应用程序线程挂起。但是新生代垃圾清理的速度比较快。
CMS在执行一次垃圾回收的过程一共分为4个阶段:
- 初始标记:根据GCroot,找出直接被引用的对象,但是不深入递归标记
- 并发标记:把初始标记中的对象,深入递归标记,标记递归的对象是否被引用
- 重新标记:由于在标记过程中,会重新产生对象,把新产生的对象重新标记,同时也会把第二阶段标记的对象重新标记
- 并发清理:把未标记的垃圾对象并发处理
其中初始标记和重新标记是会 stop the world
在并发清理过程中, 用户线程产生的新的对象由于没有被标记,会被清理掉吗?
CMS是标记清除算法,老年代堆的内存是不规整的,已使用的内存和空闲的内存相互交错在一起,虚拟机就必须维护一个列表,记录上那些内存块是可用,在分配时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”,与其相对的分配方式是“指针碰撞”,由堆内存是否规整决定使用那种分配方式。
并发清理时,肯定会有一个白色对象集合,根据白色对象集合遍历清除对象所占用的空间。不妨想象一下,开始垃圾回收前,虚拟机创建三个集合白色对象集合、灰色对象集合、黑色对象集合。刚开始时老年代的对象都是白色,则都在白色对象集合中,随着标记过程开始,白色对象开始转移到灰色对象集合中,再由灰色集合转移到黑色对象集合中,最终只留下白色对象集合和黑色对象集合有对象,除了白色对象集合和黑色对象集合使用以外的内存就是空闲列表了。
\
并发清理时是根据白色对象集合去清理对象,此时用户线程新产生的老年代对象是分配的空闲列表中,不会影响白色对象集合的内容,清除时就保证了对新产生的对象不产生影响,这些新产生的对象将在下次垃圾回收时进行处理。
\
CMS垃圾回收的缺点
1.并发回收垃圾导致CPU资源紧张,由于垃圾回收是占用系统的CPU资源的。
CMS的垃圾回收线程是比较耗费CPU资源的。CMS默认启动的垃圾回收线程的数量是(CPU核数 + 3)/ 4。我们用最普通的2核4G机器和4核8G机器来计算一下,假设是2核CPU,本来CPU资源就有限,结果此时CMS还会有个“(2 + 3) / 4” = 1个垃圾回收线程,去占用宝贵的一个CPU。
2.Concurrent Mode Failure问题
由于CMS垃圾回收的时候,系统是不停运行的,所以总会有一些对象进入老年代。这些对象不会马上清理,而是等待下一次GC清理。这些称为浮动垃圾。
所以JVM必须预留一些空间来存储这些新建的对象。当在垃圾回收器未完成工作的时候,进来的对象过大,老年代空间不足。就会出现Concurrent Mode Failure问题,就是并发回收模式失败。会直接采用Serial Old 替代CMS,并且STW,处理完后再恢复。
所以 自动触发CMS垃圾回收的比例需要合理优化一下,避免“Concurrent Mode Failure”问题。
“-XX:CMSInitiatingOccupancyFaction”参数可以用来设置老年代占用多少比例的时候触发CMS垃圾回收,JDK 1.6里面默认的值是92%。
3.内存碎片问题
由于CMS采用标记清除的算法,会导致删除后的对象,在内存中产生不连续的内存空间,会导致空间利用率不高。这个叫做内存碎片。
内存碎片是可以清除的,只要打开CMS的内存整理机制,就会在CMS垃圾回收后对老年代的内存空间进行整理。具体执行频率也是可以配置的
-XX:+UseCMSCompactAtFullCollection 使用内存整理机制
-XX:CMSFullGCsBeforeCompaction=0 这里的0表示每次执行完CMS垃圾回收后都进行内存整理