CMS与三色标记算法
CMS与三色标记算法
CMS是一款里程碑式的垃圾收集器,在它之前GC线程和工作线程是无法同时运行的,在用户层面带来的后果就是运行一段时间就会卡顿一会,降低响应速度
但在我们认知中,GC时停止其他线程应该是必要的。试想一下如果它们同时运行,如果原本不是垃圾,工作线程将引用更改变成了垃圾,这种情况大不了就是GC下一次再清理就好了,但如果原本是垃圾,在清理过程中其他线程将其引用,GC回收后就会造成灾难的程序运行错误。
CMS是怎么解决的呢?
主要步骤
Concurrent Mark Sweep,从名字上看就能知道这是一个工作在老年代的垃圾收集器,它采用了标记清除法。在它运行中大致分为几个过程
初始标记 -> 并发标记 -> 重新标记 -> 并发清理
初始标记阶段并不会标记所有垃圾,而是对GCroot直接关联的对象进行标记,这个过程依然是STW的,但它的所占时间非常短,几乎可以忽略,因为这时候并不会进行标记以外的工作
在初始标记结束后,对标记的根关联对象进行dfs,以这些对象为根,遍历整个内存堆;这个过程耗时会较长,但好在该过程并不会触发STW,工作线程依然可以运行,当然响应的速度会受到影响
由于在并发标记过程中其他线程仍在工作,那么必然会产生错标,那么就得把它修正回来,这个时候就进入了重新标记阶段,在这个阶段会触发STW,但由于错标的对象不会太多,该过程并不会停顿过长,最后进行并发清理
三色清理算法
在整个过程中,最具神秘感的便是第二个阶段,并发标记。上文所述它能在不STW下进行垃圾标记,那么是怎么做到的呢。其采用的算法叫 三色标记法
三色标记法将对象的颜色分为黑 灰 白
白色: 未被标记过的对象(垃圾)
灰色: 已经被标记过,但该对象关联系下的其他节点对象未被全部标记
黑色: 已经被标记过且该对象下关联的其他节点也已经全部标记完成
流程如下:
从main方法的GC Root开始沿着它们的对象向下查找,用黑白灰的规则,标记处所有跟GC Root相连接的对象,扫描一遍结束后,进入重新标记,短暂STW,再次进行扫描,此时略过黑色对象,找出灰色对象继续标记,程序继续执行,GC线程扫描所有的白色对象进行回收
创建三个集合 黑 灰 白
初始所有对象存入白色集合
从根节点开始遍历所有对象 (初始标记,注意这里并不会进行dfs) ,将遍历到的对象加入灰色集合
对灰色集合中所有对象进行dfs,将灰色对象引用的对象从白色变成灰色,之后将此本次遍历的根节点(此灰色对象)加入黑色集合
重复 4 直到灰色中无任何对象
清理白色垃圾
B -> D消失,A->D增加
在脑海中过一遍流程,很明显这样貌似存在一些问题。如果D原先作为白色对象被灰色对象B所引用,由于清理的过程程序还在不间断运行,如果其他线程取消了B对D的引用,那么就扫描不到D了,这样还不是最严重的,至多产生一些浮动垃圾,下一次再次清理就好了。最严重的是另外一种情况,试想一下,在扫描灰色对象时,原先D属于B的引用,与此同时其他线程进行了更改,B到D的引用消失,增加了黑色对象A到D的引用,由于A属于黑色对象,如果不进行修正,再下次清理的时候,并不会对D进行扫描,到第6步的时候,D作为白色节点被清理,但A还在引用D。
针对此问题,CMS采用了 增加引用环节(Incremental Update) 来处理,也就是将A重新标记为灰色
IncrementalUpdate产生的新问题 漏标
- 在一个灰色对象正在被一个GC线程回收时,当它已经被标记过的属性指向了一个白色对象(垃圾)
- 而这个对象的属性对象本身还未全部标记结束,则为灰色不变
- 「而这个GC线程在标记完最后一个属性后,认为已经将所有的属性标记结束了,将这个灰色对象标记为黑色,被重新引用的白色对象,无法被标记
由于这个问题,重新标记环节需要从头开始扫描。由于重新标记阶段依然得STW,从CMS诞生为止,没有任何一个jdk将CMS作为垃圾收集方案,那么用那么多精力学习CMS和三色标记有什么用。并不是三色标记算法有问题,问题发生在cms采用的IncrementalUpdate算法
什么都不干的垃圾回收器 Epsilon
Epsilon是jdk11之后提供的一个垃圾收集器,它相较其他收集器更为特殊。处理内存分配但不进行回收,java堆耗尽后jvm关闭
当能准确预估程序并不会产生过多垃圾或手动进行管理,以求最高性能运行时,可选择该垃圾收集器,通常用于测量和管理程序。
最主流的垃圾收集器 G1
G1摒弃了以前的分代模型,将内存分为不固定大小的多块区域,每个区域都可以作为eden空间,old空间….(物理上不再分代,但逻辑上依然分代)。根本上的转变使得它衡量标准不再是对象属于哪个分代,而是哪块内存存放的多,回收的收益越大,这就是G1的混合GC模式
其中Humongous作为连续的内存专门用于存放大对象
Region的取值范围为 1M ~ 32M
Region的默认个数为 2048个
G1的问题在于每次回收要将年轻代的内存全部回收,当年轻代的内存较大时,一次YGC产生的STW时间较长,因此诞生了ZGC
ZGC
ZGC采用了分页算法,也是Golang垃圾回收所采用的算法。它相较G1支持TB级别的内存,最大GC停顿10ms,且内存增大的同时停顿时间不会增长,对吞吐量的影响不超过15%
它与G1一样采用了内存分块,但动态分配使得可以有1m,2m的region,且不再分年轻代老年代,在固定时间内触发gc
本文标题:CMS与三色标记算法
文章作者:meteor
发布时间:2023-08-25
最后更新:2023-08-25
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!
分享