Mark-Compact是V8老生代用于解决内存碎片的算法,先标记存活对象,再将其紧凑迁移至堆起始端,清理尾部空间;它移动对象导致引用需更新,开销较大但能保证连续内存分配。

Mark-Compact(标记-整理)是 V8 引擎在老生代堆内存中使用的主垃圾回收算法,核心目标是解决 Mark-Sweep(标记-清除)留下的内存碎片问题,同时保证存活对象能被安全、高效地保留。
Mark-Compact 的作用不是单纯“清垃圾”,而是“腾出大块连续空间”
当对象长期存活(比如全局缓存、大型数据结构),它们会晋升到老生代。这里用 Mark-Sweep 清理后,空闲内存容易变成零散小块——后续分配大对象时可能因无足够连续空间而提前触发 GC,甚至失败。Mark-Compact 就是来“物理整理”内存的:
- 先像 Mark-Sweep 一样,从根(全局变量、调用栈、DOM 引用等)出发,递归标记所有可达对象;
- 然后把所有已标记的存活对象,统一往堆内存的起始地址方向“挪动”,紧密排列;
- 最后把挪动后空出来的尾部大片内存,直接设为空闲区,供下次快速分配。
它和 Mark-Sweep 的关键区别在于“移动对象”
Mark-Sweep 只清理不移动,导致碎片;Mark-Compact 多了一步“压缩”动作。这带来两个实际影响:
- 对象地址会变 → V8 必须更新所有指向它的引用(比如栈里、其他对象属性中的指针),这是它开销略大的原因;
- 但换来的是更少的 GC 频次和更稳定的分配性能,尤其对长期运行的应用(如后台服务、富交互页面)很关键;
- 现代 V8 已支持 Parallel Compaction(并行整理),多个线程协同搬运对象,大幅缩短停顿时间。
为什么你写的代码会影响 Mark-Compact 的效率?
它不直接“看代码”,但你的引用管理决定了哪些对象会被标记为存活——也就是哪些要被挪、哪些能被清。常见干扰项包括:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
- 没清除的
setInterval或setTimeout回调,持续持有上下文对象; - 绑定后未解绑的事件监听器(
addEventListener但没removeEventListener); - 意外创建的全局变量(如漏写
var/let/const),让对象永远可达; - 闭包中长期捕获大数组或 DOM 节点,且没有释放时机。
这些都会让本该回收的对象被“根”间接引用,被迫参与每次 Mark-Compact —— 搬得越多,耗时越长,主线程卡顿越明显。
它不是每次都运行,而是在必要时才启动
V8 不会对每次 GC 都执行 Mark-Compact。通常流程是:
- 先尝试轻量级的 Mark-Sweep;
- 如果发现空闲内存碎片严重(比如连续空闲块
- Node.js 中可通过
--max-old-space-size控制老生代上限,间接影响触发频率。
不复杂但容易忽略。

















