【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元素等,以避免内存泄漏问题。

总结

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

目录
相关文章
|
2天前
|
算法 Java
Java垃圾回收(Garbage Collection,GC)是Java虚拟机(JVM)的一种自动内存管理机制,用于在运行时自动回收不再使用的对象所占的内存空间
【6月更文挑战第18天】Java的GC自动回收内存,包括标记清除(产生碎片)、复制(效率低)、标记整理(兼顾连续性与效率)和分代收集(区分新生代和老年代,用不同算法优化)等策略。现代JVM通常采用分代收集,以平衡性能和内存利用率。
24 3
|
2天前
|
Web App开发 JavaScript 前端开发
JS 哪些操作会造成内存泄露
JS 哪些操作会造成内存泄露
|
4天前
|
存储 缓存 JavaScript
JavaScript内存泄漏通常发生在对象不再需要时
【6月更文挑战第16天】JavaScript内存泄漏常由闭包引起,当不再需要的对象仍被闭包引用时,垃圾回收机制无法清理。例如,创建返回大型对象引用的闭包函数会导致内存泄漏。避免泄漏需及时解除引用,清除事件监听器,利用WeakMap或WeakSet,以及定期清理缓存。使用性能分析工具监控内存使用也有助于检测和解决问题。
18 8
|
16天前
|
JavaScript 前端开发 算法
JS垃圾回收机制
JS垃圾回收机制
|
20天前
|
存储 算法 Java
Java的内存模型与垃圾回收机制
Java的内存模型与垃圾回收机制
|
1月前
|
存储 Java 程序员
【Python 的内存管理机制专栏】深入解析 Python 的内存管理机制:从变量到垃圾回收
【5月更文挑战第18天】Python内存管理关乎程序性能与稳定性,包括变量存储和垃圾回收。变量存储时,如`x = 10`,`x`指向内存中值的引用。垃圾回收通过引用计数自动回收无引用对象,防止内存泄漏。了解此机制可优化内存使用,避免循环引用等问题,提升程序效率和稳定性。深入学习内存管理对成为优秀Python程序员至关重要。
【Python 的内存管理机制专栏】深入解析 Python 的内存管理机制:从变量到垃圾回收
|
1月前
|
JavaScript 前端开发 算法
垃圾回收:JavaScript内存管理的利器
垃圾回收:JavaScript内存管理的利器
|
1月前
|
Web App开发 监控 前端开发
深入理解JavaScript内存泄漏:原因与解决方法
深入理解JavaScript内存泄漏:原因与解决方法
|
1月前
|
存储 算法 Java
Java一分钟之-Java内存模型与垃圾回收机制概览
【5月更文挑战第16天】本文简述Java内存模型(JMM)和垃圾回收(GC)机制。JMM包括栈、堆、方法区、程序计数器和本地方法栈。GC负责回收不再使用的对象内存,常用算法有新生代、老年代和全堆GC。文章讨论了内存溢出、死锁和GC性能等问题,提出了解决方案,如调整JVM参数和优化GC策略。此外,还强调了避免内存泄漏、大对象管理及正确释放资源的重要性。理解这些概念有助于提升Java应用的性能和稳定性。
31 1
|
1月前
|
缓存 自然语言处理 JavaScript
JavaScript内存泄漏导致应用性能下降,常见于闭包使用不当
【5月更文挑战第14天】JavaScript内存泄漏导致应用性能下降,常见于闭包使用不当。闭包能记住并访问词法作用域,若函数返回后,其引用的对象未被释放,就会引发泄漏。例如,`createLeakyFunction`创建的闭包保留了对大型对象`someLargeObject`的引用,即使函数执行完毕,对象也无法被垃圾回收。避免泄漏的方法包括及时解除引用、清除事件监听器、使用WeakMap和WeakSet以及定期清理缓存。使用性能分析工具可检测和修复内存泄漏问题。
26 3

热门文章

最新文章