从可达性分析法出发:逐步分析和掌握CMS的设计思路

241 阅读5分钟

最朴素的起点:标记-清除,简单扫一遍

CMS的全称是“并发标记-清除”,所以咱们得从标记-清除(Mark-Sweep)这个祖宗开始说起。想象一下,程序跑着,内存里堆满了对象,GC得收拾垃圾。最简单粗暴的办法就是:从根(比如栈上的变量、全局对象)出发,沿着引用链标记所有活着的对象,没标记的就当垃圾扫掉,内存回收,完事儿。

Demo举例

假设内存里有5000个对象,根引用了100个,这100个又连着200个,GC跑一趟,标记这300个活着的,剩下4700个没标记,清掉就行了。听起来挺美,对吧?

问题1:全停顿,程序卡死

但这朴素法有个大毛病——得停下程序(Stop-The-World)。为啥?因为标记的时候,程序要是还跑着,引用关系变来变去,GC就懵了。假设标记过程花了1秒,5000个对象不算多,但要是内存里有50万个对象呢?得10秒!用户那边就感觉程序卡死了,体验直接崩。

问题2:碎片堆积,内存乱糟糟

清完垃圾,内存里活着的对象东一块西一块,像个破筛子。假设你清掉4700个对象,剩下300个散落在各处,下次要分配个200KB的大对象,可能愣是找不到连续空间,得挪来挪去,效率低得要命。

优化方向:别全停,收拾碎片

从这看,得让GC和程序一块跑,别老停顿;另外,碎片也得处理,不然内存迟早废了。这俩方向是不是有点现代GC的味道了?CMS就是从这儿开始进化的。


第一步改进:并发标记,别停程序

好,咱们先解决停顿问题。朴素的标记-清除得全停,那能不能让GC一边标记,程序一边跑呢?这就引出了“并发”(Concurrent)的概念。CMS的核心思路就是:尽量跟程序同时干活,少卡用户。

怎么搞?

从根开始标记活对象的时候,程序还能继续操作内存。假设有5000个对象,GC慢慢标记300个活的,程序同时可能new出100个新对象。只要GC能跟上节奏,标记结束后清垃圾,用户几乎没感觉。

问题1:并发带来的混乱

但并发不是白来的,程序跑着,引用关系会变。比如GC标记到一半,对象A本来指向B,程序突然把A指向C,B没人用了,但GC可能已经标记了B,漏掉了C。结果就是“浮动垃圾”——有些该死的没清掉,内存回收不彻底。

问题2:清不干净,得再跑

假设一次并发标记后,浮动垃圾占了50MB,内存没全回收干净。GC得下次再跑一趟,效率就打了折扣。更糟的是,要是内存用得太猛,GC跟不上,可能会触发全停顿(Full GC),那就又回到老问题了。

优化方向:精准标记,控制停顿

得想办法让标记更准,少漏垃圾;同时,得保证GC不会老触发全停顿。这就指向了CMS的实际设计:多阶段标记+尽量并发。


CMS的真面目:多阶段并发标记-清除

CMS就是在并发标记-清除的基础上,加了点巧妙的设计,分了好几步走,尽量减少停顿。咱们拆开看看:

1. 初始标记(停顿)

GC先停一下程序,快速标记根直接引用的对象。比如5000个对象,根有10个引用,标记这10个很快,可能就10毫秒,停顿短得用户感觉不到。

2. 并发标记(不停)

然后GC和程序一起跑,从这10个根出发,沿着引用链标记所有活对象。假设找到300个活的,花了500毫秒,但程序没停,用户无感。

3. 预清理+重新标记(短停顿)

并发标记可能漏标(浮动垃圾问题),所以得再停一下,修正漏掉的。CMS会先预清理,分析变化,再快速重新标记。比如修正50个漏标对象,停顿20毫秒,还是很快。

4. 并发清除(不停)

最后,GC把没标记的垃圾清掉,比如4700个对象,边清边让程序跑,用户照样开心。

数字敏感点

假设内存1GB,新生代用别的GC(比如ParNew),CMS管老年代800MB。每次并发清除回收400MB,但浮动垃圾可能留50MB,实际回收350MB。只要程序分配速度别超过350MB/秒,CMS就能撑住。


CMS的坑和优化方向

CMS已经很牛了,低停顿、并发跑,但也不是没毛病。

问题1:浮动垃圾跑不掉

并发标记总会漏点垃圾。比如老年代800MB,浮动垃圾占50MB,回收不彻底,内存压力大了就得Full GC,停顿又回来了。

问题2:碎片还是老大难

CMS还是标记-清除,碎片没解决。假设老年代剩300MB活对象,散成1000块,下次分配个100MB对象,可能直接失败,得Full GC收拾。

优化方向:碎片整理+更强并发

  • 碎片:得加整理功能,把活对象挪一起,像G1那样分区管理,或者ZGC直接用标记-整理。
  • 并发:得让Full GC也尽量少,比如预测内存用量,提前回收。这不就跟现代GC的趋势一致了嘛,比如G1的区域化、ZGC的超低停顿。

总结:从朴素到CMS,再往现代走

  • 朴素标记-清除 → 停顿长、碎片多,得优化。
  • 并发标记 → 减少停顿,但漏垃圾。
  • CMS多阶段 → 低停顿+并发,主流雏形有了。
  • 现代方向 → 碎片收拾干净、并发更强。