深入理解 JavaScript 垃圾回收机制:标记清除与引用计数

作者:函数茶馆时间:2026-10-28 01:32:11浏览:648

本文深入解析 JavaScript 自动垃圾回收机制的核心原理,重点对比标记清除法与引用计数法的工作方式。通过具体代码示例展示引用计数导致的内存泄漏问题,并介绍手动解除引用的解决方案,帮助开发者理解如何优化内存使用。

JavaScript 垃圾回收机制概述

对于经常使用 JavaScript 的开发者而言,理解其垃圾回收机制至关重要。与 C 或 C++ 等需要手动管理内存的语言不同,JavaScript 提供了自动化的内存管理。开发者在编写代码时无需手动分配或释放内存,系统会自动处理内存分配及无用内存(即垃圾)的回收。其核心逻辑是识别并释放不再使用的变量所占用的内存空间。为此,垃圾回收机制会按照固定的时间间隔,周期性执行这一回收操作。

标记清除法与引用计数法原理

以局部变量为例,当函数运行时,系统会在栈内存或堆内存中为其分配空间。一旦函数执行完毕,局部变量便失去了存在的必要,系统会立即释放这部分内存供其他变量使用。这是最基础的内存释放方式。然而,在许多复杂场景中,变量会长期存在,此时垃圾回收机制必须持续跟踪变量的使用状态,以判断是否可以安全释放其内存。目前,JavaScript 主要采用两种策略来判断是否释放内存:标记清除法(Mark-and-Sweep)和引用计数法(Reference Counting)。 标记清除法的核心思想是“环境”。每个变量都有其运行的上下文,例如局部变量运行在函数体内。当函数启动时,进入环境的变量会被标记为“活跃”;当函数执行结束,变量脱离其运行环境时,则被标记为“非活跃”。垃圾回收器会在下一个回收周期中,清理所有被标记为非活跃的变量。 引用计数法则通过跟踪每个值的引用次数来工作。当一个变量被赋值为一个引用类型时,该值的引用计数初始为 1。如果同一个值被赋给另一个变量,引用计数加 1;反之,如果包含该引用的变量被重新赋值,引用计数减 1。当引用计数降为 0 时,意味着无法再通过任何变量访问该值,系统即可回收其内存。当垃圾回收器在下一个周期运行时,会释放所有引用计数为零的值所占用的内存空间。(参考:《JavaScript 高级程序设计》第二版)

以下代码演示了引用计数法的具体行为:

function countMethod(){
var object1 = new Object(); // 声明变量,计数器由 0 变为 1
var object2 = new Object(); // 声明变量,计数器由 0 变为 1
object1.method1 = object2; // object1 计数器 -1,object2 计数器 +1
object2.method2 = object1; // object1 计数器 +1,object2 计数器 -1
            }

引用计数导致的内存泄漏

当此函数执行退出后,object1 和 object2 的引用计数均读数为 1。这意味着两个对象相互引用,导致它们的引用计数都无法归零。因此,即使函数执行完毕,这两个变量也不会被销毁。如果函数体内存在大量此类相互引用的对象,将导致大量内存无法被回收,从而引发内存泄漏。



解决上述问题的方法是手动释放 object1 和 object2 所占用的内存,即切断它们之间的引用:


代码如下:

object1.method1 = null;
object2.method2 = null;

正常情况下的内存回收

为了对比,下面展示一个正常情况下的引用计数示例:


代码如下:

function countMethod(){
var object1 = new Object(); // 声明变量,计数器由 0 变为 1
var object2 = new Object(); // 声明变量,计数器由 0 变为 1
object1.method1 = "This is object1"; // object1 计数器 -1,object1 读数变为0
object2.method2 = "This is object2"; // object2 计数器 -1,object2 读数变为0
            }

通过上述示例可以看出,在正常情况下,当函数运行结束后,object1 和 object2 的引用计数均变为 0。在下一个垃圾回收周期中,它们将被自动回收并释放所占用的内存空间。

作者最新文章