增量标记是将长GC停顿切分为多次微暂停,利用空闲时机分步完成三色标记;若主线程长期繁忙或内存压力大,会退化为单次长停顿。

增量标记不是消除停顿,而是把一次几百毫秒的主线程冻结,拆成几十次每次不到1毫秒的微暂停,穿插在渲染帧之间、事件处理空隙里完成。用户几乎感觉不到卡顿,但页面依然保持响应。
理解它怎么“切片执行”
传统垃圾回收会从全局变量、调用栈等根对象出发,一口气遍历整个堆,标记所有存活对象——堆越大,耗时越长,主线程就卡得越久。增量标记则把这项工作切成小块:
- 每次只扫描几百个对象,或一个内存页,完成后立刻交还控制权给 JS 执行
- 用三色标记模型(白-未访问 / 灰-待扫描 / 黑-已扫描)记录中间状态,中断后可准确恢复
- V8 自动监听空闲时机:比如 requestIdleCallback 触发时、一帧渲染刚结束、setTimeout 回调前的间隙
哪些代码会让它“退化回长停顿”
增量标记依赖可中断性,一旦主线程长时间没空,它就会积压任务,最终被迫一次性扫完——等于回到卡顿原点:
- 单次同步脚本超过10ms(如巨型 for 循环、复杂 JSON 解析)会挤占所有空闲窗口
- 闭包长期持有大数组或 DOM 节点,导致对象滞留老生代,反复被扫描
- 频繁使用 setTimeout/setInterval 且未清理,让引用链无法释放,干扰标记收敛
- 大量临时字符串拼接(如循环中反复 + '')快速填满新生代,连带加剧老生代压力
开发者能做的实际优化
你不能开启或关闭增量标记,它是 V8 自动调度的;但你可以减少它被迫“放弃切片”的机会:
- 动画或滚动过程中避免每帧 new 对象(如 Path、ColorFilter、匿名函数),改用对象池或复用实例
- 在 onDraw、useEffect 或渲染逻辑里,不用 String.format、new Set()、JSON.stringify 等隐式分配操作
- 及时 clearTimeout / clearInterval,解除对大对象的意外引用绑定
- 用 Chrome DevTools Performance 面板观察 GC 事件:若 Marking 出现多个短条(≤2ms,间隔几毫秒),说明增量生效;若只有一段 >20ms 的长条,则已退化
别指望它解决所有卡顿
高频对象创建本身不直接触发长停顿,但它会加速内存填满、压缩空闲窗口、抬高 GC 频率——累积起来就是持续抖动。增量标记缓解的是单次停顿,不是总开销。真正稳帧,还得靠控制分配节奏、缩短对象生命周期、复用关键资源。

















