JavaScript的内存管理主要分为内存分配、内存使用和垃圾回收三个阶段,其中垃圾回收机制是自动释放无用内存的核心环节,它会在合适的时间点识别并清理不再被引用的内存空间,避免内存被无意义占用。

JavaScript垃圾回收的核心原理
垃圾回收的核心目标是识别哪些内存已经不再被程序使用,然后将其释放回内存池。在JavaScript中,判断内存是否可用的核心依据是可达性,即从根对象出发,是否能够访问到对应的内存引用。根对象通常包括全局对象、当前执行栈中的局部变量和参数、当前嵌套调用链上的其他变量和函数等。
常见的垃圾回收算法
1. 引用计数算法
引用计数是最早出现的垃圾回收算法,它的思路是为每个内存对象维护一个引用计数器,当该对象被其他变量引用时计数器加1,当引用失效时计数器减1,当计数器变为0时,说明该对象已经没有任何引用,就可以被回收。
这种算法的实现逻辑比较简单,示例代码如下:
// 引用计数示例
let obj1 = { name: "test" }; // 对象被obj1引用,引用计数为1
let obj2 = obj1; // 对象被obj2引用,引用计数为2
obj1 = null; // obj1引用失效,引用计数减为1
obj2 = null; // obj2引用失效,引用计数减为0,对象可被回收
但引用计数算法存在明显的缺陷,就是无法处理循环引用的问题。如果两个对象互相引用,即使它们已经不再被外部访问,引用计数也不会变为0,就会导致内存泄漏。
// 循环引用示例
function createCycle() {
let objA = {};
let objB = {};
objA.ref = objB; // objA引用objB
objB.ref = objA; // objB引用objA
return "cycle created";
}
createCycle();
// 函数执行完后,objA和objB的引用计数都为1,无法被回收,造成内存泄漏
2. 标记清除算法
标记清除算法是目前主流JavaScript引擎采用的垃圾回收算法,它解决了引用计数的循环引用问题。该算法分为两个阶段:
- 标记阶段:从根对象出发,遍历所有可达的对象,给这些对象打上标记,表示它们是活跃的,还在被使用。
- 清除阶段:遍历所有对象,把没有标记的对象视为垃圾,释放它们占用的内存,同时清除活跃对象的标记,为下一次垃圾回收做准备。
标记清除算法的示例逻辑如下:
// 标记清除逻辑模拟
// 假设根对象为global
let global = {};
// 活跃对象,从global可达
global.activeObj = { data: 1 };
// 垃圾对象,没有从global可达的引用
let garbageObj = { data: 2 };
garbageObj = null;
// 标记阶段:从global出发,global.activeObj会被标记
// 清除阶段:未被标记的garbageObj会被回收
标记清除算法虽然解决了循环引用问题,但在清除阶段会产生内存碎片,因为被回收的对象内存空间可能不是连续的,后续如果需要分配大对象,可能会因为没有足够的连续内存空间而触发额外的垃圾回收。
常见垃圾回收优化策略
为了提升垃圾回收的效率,现代JavaScript引擎还引入了一些优化策略:
- 分代回收:将对象分为新生代和老生代,新生代对象存活时间短,采用复制算法频繁回收;老生代对象存活时间长,采用标记清除或标记整理算法低频回收,减少回收开销。
- 增量回收:将完整的垃圾回收过程拆分成多个小步骤,穿插在程序执行过程中,避免长时间暂停主线程,减少页面卡顿。
- 闲时回收:在CPU空闲时触发垃圾回收,尽量不影响用户正常使用应用。
如何避免内存泄漏
虽然JavaScript有自动垃圾回收机制,但如果开发者写出不合理的代码,还是会导致内存泄漏,常见的问题和优化方法如下:
| 常见内存泄漏场景 | 优化方法 |
|---|---|
| 意外声明的全局变量 | 使用let、const声明变量,避免未声明的变量挂载到全局对象上 |
| 被遗忘的定时器或回调 | 不需要的定时器及时用clearInterval、clearTimeout清除,事件监听在不需要时移除 |
| 脱离DOM的引用 | DOM元素被移除后,及时清除对应的JavaScript引用 |
总之,理解JavaScript垃圾回收机制的工作原理,能够帮助开发者写出更合理的代码,减少内存泄漏问题,提升应用的运行稳定性。
JavaScript内存管理垃圾回收机制引用计数标记清除修改时间:2026-06-25 11:21:27