详解JavaScript的垃圾回收机制

作者:小括号

时间:2026-10-24

浏览:660

本文深入解析JavaScript自动垃圾回收(GC)机制,阐述内存分配与释放原理。详细对比引用计数、标记清除及标记整理算法的优劣,重点剖析V8引擎采用的分代回收策略,包括新生代的Scavenge算法与老生代的增量标记优化,帮助开发者理解内存管理并避免循环引用导致的内存泄漏。

文章目录

为什么需要垃圾回收(GC)

  • 如同人类随年龄增长需要清理身体代谢废物,程序在长期运行中也会产生冗余数据。若这些“垃圾”数据堆积过多,将严重拖慢程序运行速度,影响性能。
  • 在JavaScript中,字符串、对象和数组等数据的内存占用并非固定不变,而是采用动态分配策略,仅在数据真正被使用时才分配相应的内存空间。
  • 当这些数据不再被使用时,必须及时释放其所占内存,以便供其他数据复用。否则,可用内存耗尽将导致程序运行缓慢甚至崩溃。

什么是垃圾回收

垃圾回收机制,简称Garbage Collection(Garbage Collection,GC)。JavaScript内置了自动垃圾回收机制,通过特定的回收算法识别并移除不再被引用的变量或属性。JS引擎会按照固定的时间间隔,周期性释放这些无用数据占用的内存空间。相比之下,C/C++等语言需要程序员手动管理内存回收。

垃圾产生

当一个对象没有任何变量或属性对其进行引用时,该对象将无法被操作,成为“垃圾”。若此类垃圾对象大量存在,会占用巨额内存空间,导致程序响应变慢。

例如:

为什么需要垃圾回收(GC)

首先声明一个Person变量,它引用了对象{name: "江流", age: 20};随后将Person变量重新指向另一个对象{name: "心猿", age: 5000}。此时,前一个被引用的对象因失去引用而成为无用对象,无法再被访问或操作,即成为垃圾。

若此类垃圾对象不断累积且未被释放,将大量占用系统资源,严重影响性能,严重时甚至导致程序崩溃。因此,必须通过垃圾回收机制释放这部分内存。

在这一过程中,开发者无需也不能手动执行垃圾回收操作。

开发者只需确保将不再使用的对象显式设置为null即可。

垃圾回收策略

JavaScript内存管理的核心概念是“可达性”。凡是以某种方式可访问或使用的值,均视为需要保留在内存中;无法访问且无法使用的值,则由垃圾回收机制进行回收。

垃圾回收并非实时进行,因为JavaScript是单线程语言。每次执行垃圾回收都会导致程序应用逻辑暂停,待回收完成后再恢复执行,这种现象称为“全停顿”。因此,垃圾回收通常安排在CPU空闲时进行。

如何有效识别“垃圾”是垃圾回收的核心难点,以下是几种常见的算法策略(本文重点介绍前两种):

  1. 引用计数算法
  2. 标记清除算法
  3. 标记整理
  4. 分代回收

引用计数标记

策略思想:

  • 跟踪并记录每个变量值的被引用次数。
  • 当声明一个变量并将引用类型数据赋值给它时,该数据的引用次数标记为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,等待下一轮垃圾回收。

为什么需要垃圾回收(GC)

标记清除算法优缺点

优点:

  • 实现简单,标记状态无非是“打”与“不打”两种,通过二进制(0和1)即可表示。
  • 能够回收存在循环引用的对象。
  • 是V8引擎使用最多的算法。

缺点:

清除垃圾后,剩余对象的内存位置保持不变,导致空闲内存空间不连续,产生内存碎片。由于剩余空间非整块,增加了内存分配的复杂性。

标记整理算法

标记整理(Mark-Compact)算法旨在解决上述问题。在标记结束后,该算法会将无需清理的对象向内存一端移动,最后清理边界处的内存。

为什么需要垃圾回收(GC)

V8引擎的垃圾回收

  • V8引擎的垃圾回收采用标记清除法与分代回收法。
  • 内存分为新生代和老生代。

针对不同对象采用不同算法:

(1)新生代:对象存活时间较短,包括新生对象或仅经过一次垃圾回收的对象。

(2)老生代:对象存活时间较长,经历过一次或多次垃圾回收的对象。

回收新生代对象

回收新生代对象主要采用复制算法(Scavenge算法)结合标记整理算法。其中,Scavenge算法的具体实现主要基于Cheney算法。

对象晋升机制

经历一轮GC后仍存活的新生代对象需要晋升至老生代。

回收老生代对象

回收老生代对象主要采用标记清除、标记整理及增量标记算法,其中主要使用标记清除算法;仅在内存分配不足时,才采用标记整理算法。

  • 首先使用标记清除完成垃圾空间的回收;
  • 采用标记整理进行空间优化;
  • 采用增量标记进行效率优化;

参考文档:

JS垃圾回收机制

JavaScript GC 垃圾回收机制

总结

本文到此结束,希望能为您提供帮助。欢迎您关注本站以获取更多相关内容!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章