JavaScript垃圾回收核心是基于可达性判断对象是否可回收:从根对象(如全局变量、执行栈变量)出发,能通过引用链访问到的对象被视为存活,否则即被标记清除;主流采用标记-清除算法,可正确处理循环引用,而引用计数法因无法解决该问题已被弃用。

JavaScript 的垃圾回收(GC)是自动运行的,不靠开发者手动触发。它核心只做一件事:识别并释放那些“再也用不到”的对象所占的内存。关键不是时间或操作次数,而是对象是否还被程序“ reachable”——也就是能否从根对象出发,顺着引用链访问到它。
可达性决定生死
垃圾回收器不会看变量有没有 var 或是否 delete 了,它只认“能不能找到”。以下对象默认是根(Root):
- 全局作用域中的变量(包括 window / globalThis 上挂的属性)
- 当前函数调用栈里的局部变量
- 正在执行中定时器、事件监听器、Promise 回调里捕获的闭包变量
只要一个对象能通过任意一条引用路径从这些根到达,它就被视为“存活”,不会被回收。反之,哪怕你写了 obj = null,只要还有别的变量或闭包在引用它,它依然安全。
主流算法:标记-清除(Mark-and-Sweep)
现代 JS 引擎(V8、SpiderMonkey 等)基本都用这个算法,分两步:
立即学习“Java免费学习笔记(深入)”;
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 标记阶段:从所有根开始遍历,递归标记所有能访问到的对象
- 清除阶段:扫描整个堆,把没被标记的对象内存直接释放
它天然解决循环引用问题——比如 A.ref = B; B.ref = A;,只要 A 和 B 都不再被根可达,整组都会被一并回收。早期的引用计数法(每个对象记被引多少次)因无法处理这类情况,已被弃用。
内存实际怎么释放?
释放不是“擦除数据”,而是把那块内存标记为“空闲”,供后续新对象分配使用:
- 栈内存:函数执行结束,对应栈帧自动弹出,空间立即可用
- 堆内存:GC 清除后,该内存块加入空闲链表;下次 new Object() 或字面量创建对象时,引擎从中分配
- 部分引擎(如 V8)还会在清除后做压缩(Compact),把存活对象挪到一起,减少内存碎片
什么情况下内存不会被及时释放?
不是 GC 失灵,而是对象仍被意外持有,常见有:
- 全局变量无意中保留了大对象引用(比如缓存没清理)
- 事件监听器没用 removeEventListener 解绑,且回调里用了外部变量形成闭包
- 定时器(setInterval)没 clear,回调持续活跃
- DOM 元素被移除,但 JS 里还存着对它的引用(尤其用 jQuery 时容易忽略)
这些都会让对象保持可达,GC 只能跳过它们——结果就是内存占用缓慢上涨,即内存泄漏。

















