增量标记为解决传统标记-清除算法导致的长时间STW停顿而设计,它将标记过程拆分为多个小任务穿插执行,配合写屏障与空闲调度,使单次停顿从数百毫秒降至几毫秒,显著提升交互响应流畅度。

现代 JavaScript 引擎(比如 V8)用“增量标记”把原本一次性的大任务拆成多个小步骤,穿插在 JS 正常执行之间完成,从而大幅压缩单次停顿时间——从可能几百毫秒降到几毫秒以内。
为什么需要增量标记?
传统标记-清除算法的标记阶段必须遍历整个对象图,一旦堆内存较大(比如几百 MB),这个过程会持续较长时间。期间 JS 执行完全暂停(Stop-The-World),用户会明显感知卡顿,尤其在动画、输入响应等敏感场景中不可接受。
增量标记的核心目标不是“不暂停”,而是“不长时间暂停”。它承认暂停不可避免,但把大块暂停切成多个微小暂停,让主线程仍有空隙处理用户交互和渲染任务。
增量标记是怎么工作的?
它把标记过程分解为若干小单元,每个单元只处理一部分对象,并在 JS 代码继续运行前主动交还控制权。关键机制包括:
立即学习“Java免费学习笔记(深入)”;
- 标记任务分片:GC 将待标记的对象图按结构或内存页切分成小批次,每次只扫描一个批次(例如几百个对象或一页内存)
-
写屏障(Write Barrier)配合:JS 运行中若修改了对象引用(如
a.b = c),写屏障会记录下这个变化,确保后续增量轮次能重新检查受影响区域,避免漏标 - 空闲时间调度:V8 会监听主线程空闲时段(如事件循环空档、帧渲染后),自动插入下一个标记小任务,尽量避开高优先级任务
开发者能观察到什么?
在 Chrome DevTools 的 Performance 面板录制时,你可能看到一连串短小的 Minor GC 或 Major GC 事件,其中 Mark 阶段被标注为“IncrementalMarking”;Memory 面板的堆快照时间也会更稳定,不再出现单次超长 GC 卡顿。
注意:增量标记不能消除所有停顿——清除阶段仍需短暂停顿,且当内存压力剧增时,V8 可能触发非增量的完整标记来兜底。但它显著提升了日常场景下的响应流畅度。
对代码编写有什么提示?
增量标记虽由引擎自动管理,但它的效率依赖于对象图的可遍历性。以下做法有助于它更高效工作:
- 避免在标记过程中频繁修改深层对象引用链(如反复重赋值嵌套属性),这会触发大量写屏障开销
- 减少超大对象或超深引用树(比如百万级数组嵌套对象),这类结构会让单个增量片段变重
- 及时解除已失效的引用(如清空闭包中不再需要的大数组、移除未清理的事件监听器),缩小待标记图规模


















