【JavaScript】垃圾回收与内存泄漏

简介: JavaScript的*垃圾回收机制*是一种自动化的内存管理机制,用于检测和回收不再使用的内存资源,以便重新分配给其他需要的部分。JavaScript中的垃圾回收器负责跟踪和管理内存的分配和释放,使开发人员无需手动管理内存。 *内存泄漏*指的是程序中分配的内存空间无法被释放和回收,并且随着时间推移导致可用内存逐渐减少。

引言

JavaScript的垃圾回收机制是一种自动化的内存管理机制,用于检测和回收不再使用的内存资源,以便重新分配给其他需要的部分。JavaScript中的垃圾回收器负责跟踪和管理内存的分配和释放,使开发人员无需手动管理内存。

内存泄漏指的是程序中分配的内存空间无法被释放和回收,并且随着时间推移导致可用内存逐渐减少。

垃圾回收机制

浏览器的 Javascript 具有自动垃圾回收机制(GCGarbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。

其原理是:垃圾收集器会定期(周期性)找出那些不再继续使用的变量,然后释放其内存

但是这个过程不是实时的,因为其开销比较大并且 GC 时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。

不再使用的变量也就是生命周期结束的变量,当然只能是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。局部变量只在函数的执行过程中存在,而在这个过程中会为局部变量在栈或堆上分配相应的空间,以存储它们的值,然后在函数中使用这些变量,直至函数结束,而闭包中由于内部函数的原因,外部函数并不能算是结束。

JavaScript中的垃圾回收机制主要基于以下两个原则:

1. 引用计数(Reference Counting)

这是一种简单的垃圾回收算法,它通过跟踪每个对象被引用的次数来确定是否是垃圾。当一个对象被引用时,引用计数加1;当一个对象不再被引用时,引用计数减1。当引用计数为0时,表示该对象不再被使用,可以被回收。 但是,引用计数算法无法解决循环引用问题。如果两个或多个对象相互引用,并且没有其他地方对它们进行引用,则它们的引用计数永远不会为0,导致内存泄漏。

2. 标记-清除(Mark and Sweep)

它通过标记活动对象并清除未标记对象来进行垃圾回收。

  • 标记阶段:从根对象(如全局变量、活动函数调用栈等)开始,垃圾回收器遍历对象图,并标记所有可达的对象。可达对象是指那些仍然被引用的对象。
  • 清除阶段:在标记阶段后,垃圾回收器清除未被标记的对象,即那些不再被引用的垃圾对象。这些未被标记的对象将被释放,并且内存空间可以重新分配给其他需要的部分。
  • 压缩阶段(可选):在清除阶段后,可能会产生内存碎片。为了解决这个问题,垃圾回收器可
    以进行内存压缩操作,将活动对象紧凑地放置在一起,以便更好地利用内存空间。

示例

标记清除

当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。

从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。

而当变量离开环境时,则将其标记为“离开环境”。

functiontest(){
vara=10// 被标记 ,进入环境 varb=20// 被标记 ,进入环境}
test() // 执行完毕 之后 a、b 又被标离开环境,被回收。

垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。

然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。

最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。

IE9+、Firefox、Opera、Chrome、SafariJS 使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。

引用计数

当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是 1。如果同一个值又被赋给另一个变量,则该值的引用次数加 1

相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。

这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为 0 的值所占用的内存。

functiontest() {
vara= {};    // a 指向对象的引用次数为 1varb=a;    // a 指向对象的引用次数加 1,为 2varc=a;    // a 指向对象的引用次数再加 1,为 3varb= {};    // a 指向对象的引用次数减 1,为 2}

但是,如果循环引用就会造成内存泄漏的问题,例如:

functionfn() {
vara= {};
varb= {};
a.pro=b;
b.pro=a;
}
fn();

以上代码 ab 的引用次数都是 2fn 执行完毕后,两个对象都已经离开环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为 ab 的引用次数不为 0,所以不会被垃圾回收器回收内存,如果 fn 函数被大量调用,就会造成内存泄露。


内存泄漏

1. 未清理的定时器或事件监听器

functionstartProcess() {
setInterval(() => {
// 执行一些操作  }, 1000)
}
startProcess()

在上述代码中,我们创建了一个定时器,但没有清除它。每次定时器触发时,都会执行一些操作。如果我们没有在不再需要定时器时调用 clearInterval() 方法来清除它,定时器将持续运行并占用内存资源。

解决方法

functionstartProcess() {
constintervalId=setInterval(() => {
// 执行一些操作  }, 1000)
// 在不再需要定时器时清除它setTimeout(() => {
clearInterval(intervalId)
  }, 5000)
}
startProcess()

在上述代码中,我们使用 setInterval() 创建了一个定时器,并在5秒后使用 clearInterval() 清除它。这样可以确保在一段时间后停止定时器并释放相关资源。

2. 闭包中的循环引用

functioncreateClosure() {
letdata="Sensitive Data"returnfunction() {
console.log(data)
  }
}
letclosure=createClosure()

在上述代码中,我们创建了一个闭包函数,并将其赋值给变量 closure。闭包函数中引用了外部变量 data。如果我们在使用完闭包函数后不解除对它的引用,则闭包函数和其引用的外部变量 data 将无法被垃圾回收。

解决方法

closure=null// 解除对闭包函数的引用

在上述代码中,我们将变量 closure 设置为 null,解除了对闭包函数的引用。这样,在下一次垃圾回收周期中,闭包函数及其引用的外部变量将被标记为不再使用,并被释放。

3. 未释放的DOM元素事件监听器

constbutton=document.querySelector("#myButton")
button.addEventListener("click", () => {
// 执行一些操作})

在上述代码中,我们给一个按钮元素添加了一个点击事件监听器。如果我们忘记在不再需要该按钮时移除事件监听器,该按钮元素将继续保持对事件监听器的引用,导致内存泄漏。

解决方法

constbutton=document.querySelector("#myButton")
functionhandleClick() {
// 执行一些操作}
button.addEventListener("click", handleClick)
// 在不再需要按钮时移除事件监听器button.removeEventListener("click", handleClick)

在上述代码中,我们使用 addEventListener() 添加了一个点击事件监听器,并在不再需要按钮时使用 removeEventListener() 移除它。这样可以确保在不再需要按钮时,相关的事件监听器被正确地移除,从而避免内存泄漏。

这些示例展示了一些常见的JavaScript内存泄漏场景。在实际开发中,我们应该注意及时清理不再使用的定时器、事件监听器、闭包和DOM元素等,以避免内存泄漏问题。

总结

垃圾回收是一种自动化的内存管理机制,通过标记-清除和压缩等步骤来回收不再使用的内存资源。然而,如果代码中存在内存泄漏问题,可能导致垃圾回收器无法正确释放内存。为了避免内存泄漏,需要注意及时释放资源、避免循环引用,并确保显式地解除绑定和移除不再需要的对象。

目录
相关文章
|
30天前
|
存储 缓存 JavaScript
请描述一种JavaScript内存泄漏的情况,并说明如何避免这种情况的发生。
JavaScript内存泄漏常由闭包引起,导致无用对象滞留内存,影响性能。例如,当一个函数返回访问大型对象的闭包,即使函数执行完,对象仍被闭包引用,无法被垃圾回收。防止泄漏需及时解除引用,注意事件监听器清理,使用WeakMap或WeakSet,定期清理缓存,以及利用性能分析工具检测。
13 2
|
1月前
|
JavaScript 前端开发 算法
Java Script 中的垃圾回收机制有哪些缺点
Java Script 中的垃圾回收机制有哪些缺点
13 0
|
1月前
|
JavaScript 前端开发 算法
描述 JavaScript 中的垃圾回收机制。
描述 JavaScript 中的垃圾回收机制。
20 1
|
1月前
|
Java 程序员
探讨JVM垃圾回收机制与内存泄漏
探讨JVM垃圾回收机制与内存泄漏
|
9天前
|
JavaScript 前端开发 Java
js 垃圾回收机制的方法
js 垃圾回收机制的方法
|
11天前
|
存储 缓存 监控
Java内存管理:垃圾回收与内存泄漏
【4月更文挑战第16天】本文探讨了Java的内存管理机制,重点在于垃圾回收和内存泄漏。垃圾回收通过标记-清除过程回收无用对象,Java提供了多种GC类型,如Serial、Parallel、CMS和G1。内存泄漏导致内存无法释放,常见原因包括静态集合、监听器、内部类、未关闭资源和缓存。内存泄漏影响性能,可能导致应用崩溃。避免内存泄漏的策略包括代码审查、使用分析工具、合理设计和及时释放资源。理解这些原理对开发高性能Java应用至关重要。
|
15天前
|
存储 前端开发 安全
JVM内部世界(内存划分,类加载,垃圾回收)(上)
JVM内部世界(内存划分,类加载,垃圾回收)
49 0
|
1月前
|
JavaScript 前端开发 Java
JavaScript的垃圾回收机制
JavaScript的垃圾回收机制
39 1
|
1月前
|
Java 程序员 Python
|
2月前
|
监控 Java 编译器
优化Go语言程序中的内存使用与垃圾回收性能
【2月更文挑战第5天】本文旨在探讨如何优化Go语言程序中的内存使用和垃圾回收性能。我们将深入了解内存分配策略、垃圾回收机制,并提供一系列实用的优化技巧和建议,帮助开发者更有效地管理内存,减少垃圾回收的开销,从而提升Go程序的性能。