CMS本身是个非常极端的垃圾收集器,他过于注重停顿时间,而不在乎吞吐量。正常情况使用标记清除算法,所以也会有内存碎片的产生,整个CMS垃圾收集的过程,主要是为了缩短停顿时间,所以在有些环节会与业务线程并发执行。
1.初始标记,根据GC Root的引用链,只标记第一个对象,此时是多个标记线程并行处理(jdk8默认多线程 jdk7默认单线程 我本人倾向单线程)。STW
2.并发标记:根据第一个对象的引用链并发标记,并发是与业务线程的并发,所以会有一个问题,有的垃圾对象可能因为并发的业务线程而重新引用了,此时却被CMS列为垃圾对象
3.重新标记:把没标记的对象(垃圾对象) 修改为可用对象,因为这个时候你不能百分百确定它是不是垃圾对象,所以保留,重新标记是扫描整个堆,此时STW
4.并发清除:与业务线程同时并发进行,清除垃圾,也正是因为与业务线程并发,此时还是会产生垃圾。
为什么初始标记的时候用单线程标记,而重新标记的时候用多线程标记?
CMS的主要目的是为了减少停顿时间,但还有个问题需要考虑到,应该合理的利用资源,而不是浪费资源,初始标记的过程很简单,只需要根据GC Root的引用链标记第一个对象,效率很高,没有必要用多线程,而重新标记的时候,因为是要把整个引用链上的对象又重新标记一遍,所以使用多线程,重新标记用多线程肯定比用单线程要快。所有的垃圾收集器不仅在强调吞吐量和停顿时间,还有一个就是需要合理的利用资源。
在jdk7中 初始标记默认是单线程,因为本身用jdk7的 多线程也用的不多,而jdk8中 初始标记默认变成多线程,因为用jdk8的多线程用的多
-XX:+CMSParallelInitialMarkEnabled控制是否开启
![[CMS垃圾回收流程.png]]
CMS 采用的算法: 正常情况使用标记清除算法 forkground(并发失败)的时候使用标记压缩,
用标记压缩算法是用的
单次遍历算法(减少回收次数) + Lisp2滑动整理算法(增大回收面积)
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
这两个参数表示多少次FullGC后采用MSC算法压缩堆内存,0表示每次FullGC后都会压缩,同时0也是默认值
标记压缩算法是通过参数控制是否启用的,
碎片问题也是CMS采用的标记清理算法最让人诟病的地方:Backgroud CMS采用的标记清理算法会导致内存碎片问题,从而埋下发生FullGC导致长时间STW的隐患。
所以如果触发了FullGC,无论是否会采用MSC算法压缩堆,那都是ParNew+CMS组合非常糟糕的情况。因为这个时候并发模式已经搞不定了,而且整个过程单线程,完全STW,可能会压缩堆(是否压缩堆通过上面两个参数控制),真的不能再糟糕了!想象如果这时候业务量比较大,由于FullGC导致服务完全暂停几秒钟,甚至上10秒,对用户体验影响得多大。
1.初始标记 2.并发标记 3.并发预处理 4.可终止的预处理 5.重新标记 6.并发清理
已知CMS是回收老年代对象,
![[CMS引用对象如何判断A对象是存活对象-问题.png]]
初始标记的时候,是STW, 并发标记是和业务线程并行阶段,这个时候如果老年代区已经满了,而业务线程又有其他的老年代对象需要放入Old区,这个时候是放不进去的,问题产生了,此时就会触发CMS的Foregroud CMS,按照官方的描述就是:如果并发收集器不能在老年代区域被填满之前回收完垃圾,或者说年老代中有效的空闲内存空间不能满足某个内存的分配请求,此时应用会暂停,这种暂停才是CMS最致命的问题,暂停的这段时间内开始垃圾回收,直到回收完成才会恢复应用程序,这就是CMS的并发失败,在并发失败之后会使用标记压缩算法
并发预处理:CMS的主要目的是尽可能少的停顿时间,那么如果新生代的对象少了,CMS根据老年代去找的新生代对象也就少了,这样CMS回收的速度也就变快了
并发预处理就是并发标记之后,再执行一次新生代的垃圾回收,并发预处理是为重新标记提前做好准备,减少重新标记的时间,98%的新生代对象朝生夕死,执行完一次新生代对象的回收之后,重新标记的效率将大大提高。
记忆集
记忆集是一种用于记录从非收集区域指向收集区域的指针集合的数据结构
比如老年代的对象如果在GCRoot上,那么这个对象就可以看成一个GCRoot,
他只要指向一个新生代,那么这个新生代就不用回收啦,这种指向关系,我们需要存储。卡表 稀疏表 粗粒度位图 细粒度位图都属于记忆集、
记忆集的实现方式有很多 哈希表 位图 粗粒度位图 细粒度位图 单纯的字节数组。。。
卡表CardTable
卡表就是记忆集的实现方式之一,在CMS当中,记录老年代引用新生代这种指向关系的数组 卡表就是个字节数组。
[byte1, byte2, byte3, byte4]
每个元素可称之为卡页,一个卡页描述的是512个字节的老年代区域内存地址,类似于位图的概念,每个元素都是一个字节,一个字节占8bit位, 这8个bit位 每一个位置都是用来标识的,00000000 当这8个标志位某个位置的0变为1的时候,说明变成了脏卡,0变成1说明该元素代表的Old区中的512个字节中的某个对象有跨代引用的关系,可能是老年代指向新生代,也有可能是新生代指向老年代,8个标志位都代表不同的含义。所以在重新标记的时候,只要扫描Old区的变脏的元素加入GCRoot中就行,
三色标记:
1.初始时,所有对象都在白色集合当中
2.将GC Root直接引用到的对象 挪到灰色集合当中,(初始标记只找每个GC Root中的第一个对象)
3.从灰色集合中获取对象,
4.将本对象 引用到的其他对象全部挪到灰色集合中,
5.将本对象挪到黑色集合当中
重复3,4步骤,直到灰色集合为空时结束
结束后 仍然在白色集合中的对象为不可达对象,回收
多标:在并发标记的时候,因为业务线程也在运行,此时可能会因为业务线程的开始或者结束而导致 有新的对象的产生,或者有新的垃圾对象,此时这种对象是一律当作可用对象(浮动垃圾) 不做清理的,留着下一次再清理。并发清理的时候也会有这种现象。
漏标:漏标?
写屏障
伪共享
标签:标记,对象,收集器,并发,线程,垃圾,解析,CMS From: https://www.cnblogs.com/zyonghua/p/18171852