详解JavaScript的垃圾回收机制
本文深入解析JavaScript自动垃圾回收(GC)机制,阐述内存分配与释放原理。详细对比引用计数、标记清除及标记整理算法的优劣,重点剖析V8引擎采用的分代回收策略,包括新生代的Scavenge算法与老生代的增量标记优化,帮助开发者理解内存管理并避免循环引用导致的内存泄漏。
为什么需要垃圾回收(GC)
- 如同人类随年龄增长需要清理身体代谢废物,程序在长期运行中也会产生冗余数据。若这些“垃圾”数据堆积过多,将严重拖慢程序运行速度,影响性能。
- 在JavaScript中,字符串、对象和数组等数据的内存占用并非固定不变,而是采用动态分配策略,仅在数据真正被使用时才分配相应的内存空间。
- 当这些数据不再被使用时,必须及时释放其所占内存,以便供其他数据复用。否则,可用内存耗尽将导致程序运行缓慢甚至崩溃。
什么是垃圾回收
垃圾回收机制,简称Garbage Collection(Garbage Collection,GC)。JavaScript内置了自动垃圾回收机制,通过特定的回收算法识别并移除不再被引用的变量或属性。JS引擎会按照固定的时间间隔,周期性释放这些无用数据占用的内存空间。相比之下,C/C++等语言需要程序员手动管理内存回收。
垃圾产生
当一个对象没有任何变量或属性对其进行引用时,该对象将无法被操作,成为“垃圾”。若此类垃圾对象大量存在,会占用巨额内存空间,导致程序响应变慢。
例如:

首先声明一个Person变量,它引用了对象{name: "江流", age: 20};随后将Person变量重新指向另一个对象{name: "心猿", age: 5000}。此时,前一个被引用的对象因失去引用而成为无用对象,无法再被访问或操作,即成为垃圾。
若此类垃圾对象不断累积且未被释放,将大量占用系统资源,严重影响性能,严重时甚至导致程序崩溃。因此,必须通过垃圾回收机制释放这部分内存。
在这一过程中,开发者无需也不能手动执行垃圾回收操作。
开发者只需确保将不再使用的对象显式设置为null即可。
垃圾回收策略
JavaScript内存管理的核心概念是“可达性”。凡是以某种方式可访问或使用的值,均视为需要保留在内存中;无法访问且无法使用的值,则由垃圾回收机制进行回收。
垃圾回收并非实时进行,因为JavaScript是单线程语言。每次执行垃圾回收都会导致程序应用逻辑暂停,待回收完成后再恢复执行,这种现象称为“全停顿”。因此,垃圾回收通常安排在CPU空闲时进行。
如何有效识别“垃圾”是垃圾回收的核心难点,以下是几种常见的算法策略(本文重点介绍前两种):
- 引用计数算法
- 标记清除算法
- 标记整理
- 分代回收
引用计数标记
策略思想:
- 跟踪并记录每个变量值的被引用次数。
- 当声明一个变量并将引用类型数据赋值给它时,该数据的引用次数标记为1。
- 若该引用类型数据又被赋值给另一个变量,则引用次数加1。
- 若原变量被其他值覆盖,则引用次数减1。
- 当引用次数降为0时,表示该数据已无使用价值且无法访问,垃圾回收器将在执行过程中销毁引用次数为0的数据,并回收其占用的内存空间。
例如:
let a = {
name: "江流",
age: 20
}; //此时该对象的引用计数标记为1(a 引用)
let b = a; //此时对象的引用计数标记为2(a、b 引用)
a = null; //此时对象的引用计数标记为1((b 引用))
b = null; //此时对象的引用计数标记为0(无变量引用)
... //等待GC 回收此对象
然而,这种方式存在一个严重问题——循环引用。
循环引用引发的问题
在函数内部,若对象A的属性指向对象B,而对象B的属性又指向对象A,即使函数执行完毕,A和B的引用计数器也不会归零,从而阻碍正常的垃圾回收。
例如下面的例子:
function test()
{
let A = new Object();
let B = new Object();
A.pointer= B;
B.pointer = A;
}
test();
当对象A和B相互引用时,按引用计数策略,它们的计数均为2。但在test()函数执行结束后,作用域内的对象A和B理应被GC销毁。
若此类情况反复执行,将导致严重的内存泄漏。
解决方法
在函数结束时,将相关对象指向null。
//切断引用关系
A = null;
B = null;
引用计数算法的优缺点
优点:
- 当引用计数为零时,能立即发现并回收垃圾。
- 最大限度减少程序暂停时间。
缺点:
- 无法回收存在循环引用的对象。
- 空间开销较大。
标记清除算法
核心思想
分为标记和清除两个阶段完成。
大概过程:
- 垃圾收集器在运行时给内存中所有变量加上标记,初始假设所有对象均为垃圾,标记为0。
- 随后从各个根对象开始遍历,将非垃圾节点标记改为1。
- 清理所有标记为0的垃圾对象,销毁并回收其占用的内存空间。
- 最后将内存中所有对象标记重置为0,等待下一轮垃圾回收。

标记清除算法优缺点
优点:
- 实现简单,标记状态无非是“打”与“不打”两种,通过二进制(0和1)即可表示。
- 能够回收存在循环引用的对象。
- 是V8引擎使用最多的算法。
缺点:
清除垃圾后,剩余对象的内存位置保持不变,导致空闲内存空间不连续,产生内存碎片。由于剩余空间非整块,增加了内存分配的复杂性。
标记整理算法
标记整理(Mark-Compact)算法旨在解决上述问题。在标记结束后,该算法会将无需清理的对象向内存一端移动,最后清理边界处的内存。

V8引擎的垃圾回收
- V8引擎的垃圾回收采用标记清除法与分代回收法。
- 内存分为新生代和老生代。
针对不同对象采用不同算法:
(1)新生代:对象存活时间较短,包括新生对象或仅经过一次垃圾回收的对象。
(2)老生代:对象存活时间较长,经历过一次或多次垃圾回收的对象。
回收新生代对象
回收新生代对象主要采用复制算法(Scavenge算法)结合标记整理算法。其中,Scavenge算法的具体实现主要基于Cheney算法。
对象晋升机制
经历一轮GC后仍存活的新生代对象需要晋升至老生代。
回收老生代对象
回收老生代对象主要采用标记清除、标记整理及增量标记算法,其中主要使用标记清除算法;仅在内存分配不足时,才采用标记整理算法。
- 首先使用标记清除完成垃圾空间的回收;
- 采用标记整理进行空间优化;
- 采用增量标记进行效率优化;
参考文档:
JS垃圾回收机制
JavaScript GC 垃圾回收机制
总结
本文到此结束,希望能为您提供帮助。欢迎您关注本站以获取更多相关内容!
JS语法错误会阻止加载吗?区分SyntaxError与资源加载失败
2026-11-05 16:08:46
JavaScript常见SyntaxError语法错误排查与修复方法
2026-11-05 13:58:44
JS中slice、substring和substr截取字符串的区别
2026-11-05 13:43:47
JS 转 TS 手动迁移最佳实践:从配置 tsconfig 到消除 any 类型
2026-11-05 11:53:32
为什么JS数组push后得到的是数字而不是数组?
2026-11-05 09:59:18
