JS垃圾回收机制有哪些?

简介: 本文介绍了JavaScript中的垃圾回收(GC)机制,包括其概念、产生原因及重要性。文章详细讲解了几种常见的垃圾回收算法,如引用计数、标记清除、标记整理和分代回收,并分析了它们的优缺点。最后总结了垃圾回收对JS开发的重要作用,强调了其在自动内存管理和性能优化中的关键地位。

本文首发微信公众号:前端徐徐。

概念

GC

在JavaScript中,GC代表"垃圾回收"(Garbage Collection)。垃圾回收是一种自动内存管理机制,负责监测不再使用的对象,并释放它们占用的内存空间,以避免内存泄漏和资源浪费。

JavaScript是一种高级编程语言,它在运行时动态地创建和销毁对象。当创建对象时,JavaScript引擎会为其分配内存空间,而当对象不再被引用或使用时,内存空间应该被释放以便在后续的代码执行中重新利用。垃圾回收机制就是负责自动检测和清除不再需要的对象,以释放占用的内存。

用通俗的例子来解释的话就是:

假设内存空间是一间房间,里面满满当当都堆着各种各样的东西。这时候你想在房间里面再放新的东西,就要先把一些不要的旧东西清理出去。

JS引擎就像个管家,它会定期查看房间里哪些东西不再被使用或者无法访问到了。这些被遗忘的旧物品就像是内存中的“垃圾”,会被GC收集起来扔出房间。扔出这些垃圾后,房间就空出空间来了,可以继续用来存放新的变量、对象等程序的数据了。

GC就好比是程序的内存管家,不断地检查内存使用情况,发现那些可以被清理的垃圾,然后自动回收这些内存。这对保证JS程序平滑运行非常重要。

垃圾产生原因

  1. 内存分配时产生:JS程序在运行过程中,会不断地申请内存来存储对象、变量等数据。这些内存使用完后就成了“垃圾”。
  2. 不再被引用:由于JS是动态语言,一个对象是否还被使用,只能通过是否有引用来判断。如果对象没有被任何东西引用,它就成为了垃圾。
  3. 全局变量、循环引用:全局变量、闭包、循环引用等情况会导致某些内存无法被释放,这部分内存也会变成垃圾。
  4. 程序运行产生的中间状态:像函数的局部变量等temporal 数据也会占用内存,使用完就成为垃圾

为什么要回收垃圾

  1. 管理内存使用:JS中内存的分配和释放都是自动的,为了控制内存占用,需要有垃圾回收机制来回收未使用的内存。
  2. 防止内存泄漏:由于对象之间相互引用,可能导致一些未被使用的对象无法释放,垃圾回收可以找到并释放这些对象。
  3. 释放内存:定期回收不再需要的对象内存,释放内存空间让程序重新使用。
  4. 提高性能:释放未使用的内存可以使得内存使用更高效,减少内存碎片,提高程序性能。
  5. 优化内存:垃圾回收可以按照使用情况优化内存的分配,减少内存占用。
  6. 简化开发:开发者不需要关注内存控制,垃圾回收自动完成内存管理的工作。
  7. 避免常见错误:自动内存管理可以防止手动内存控制常见的错误,例如内存泄漏、访问未分配内存等

几种机制

引用计数

引用计数(Reference Counting)是JS中较早的垃圾回收方式之一。

✴️ 基本思想:

追踪记录每个对象被引用的次数,当一个对象的引用数达到 0 的时候,说明这个对象不再被使用,那么就可以将其占用的内存空间回收释放。每个对象有一个引用计数属性,初始值为0 ==> 当对象被引用时,其计数器加1 ==> 当引用失效时,计数器减1 ==> 当计数器为0时,表示对象不再需要,该对象的内存空间可以被回收。

优点:

  1. 实现简单,资源消耗较少
  2. 可以立即回收内存,不需要等到执行周期结束
  3. 最大限度地减少程序暂停时间

缺点:

  1. 循环引用问题:如果两个或多个对象相互引用,它们的引用计数永远不会变为0,即使它们已经不再被程序访问,也不会被回收,从而导致内存泄漏。
  2. 计数器更新开销:在引用计数中,每当对象被引用或取消引用时,都需要更新计数器。这个开销会影响性能,特别是在存在大量对象的情况下。
  3. 不可达对象问题:引用计数无法处理循环引用之外的其他类型的不可达对象。例如,如果有一个对象A引用了B,而B又引用了C,但C不再被任何其他对象引用,那么C将无法被回收。

由于以上问题,现代的JavaScript引擎通常不再采用纯粹的引用计数作为主要的垃圾回收机制。

标记清除

标记清除(Mark and Sweep)是JavaScript中最常见的垃圾回收机制之一。它是一种用于自动内存管理的算法,用于检测和释放不再使用的对象,以避免内存泄漏和内存溢出。

✴️ 基本思想:

  1. 标记阶段:垃圾回收器从根对象(通常是全局对象、活动执行栈和闭包等)出发,遍历所有可访问的对象,并标记为活动对象。在这个阶段,垃圾回收器会识别出所有被引用的对象,将其标记为“存活”。
  2. 清除阶段:在标记阶段完成后,垃圾回收器会对堆内存进行扫描,清除所有未标记的对象,这些对象被认为是“垃圾”,因为它们不再被任何活动对象引用。清除阶段会释放这些垃圾对象所占用的内存空间,使其可用于未来的对象分配。

标记清除算法通过这两个阶段实现垃圾对象的回收。在标记阶段,垃圾回收器会确定哪些对象仍然处于活动状态。在清除阶段,垃圾回收器会清理所有未被标记的对象,释放内存空间。

优点:

  1. 标记和清除两个阶段执行效率较高
  2. 不需要额外的内存开销
  3. 可以立即回收可回收对象的内存

缺点:

  1. 会产生内存碎片,降低内存利用率
  2. 需要暂停程序执行进行完整垃圾回收,可能造成较长的停顿时间
  3. 清除时需要遍历所有对象,比较低效
  4. 标记和清除效率依赖于对象数量,对象越多越慢
  5. 不易实现增量式垃圾回收

标记整理

标记整理(Mark-and-Compact)是在基本标记清除算法基础上的改进。

✴️ 基本思想:

  1. 标记阶段:同标记清除一样,首先标记所有正在被引用的对象。
  2. 整理阶段:与直接清除不同,标记整理会先执行一次内存整理。将存活对象向内存空间一端移动,然后直接清理掉端边界以外的内存。
  3. 清除阶段:空间端边界以外的内存就全部可以直接回收掉了。

优点:

  1. 减少内存碎片,内存利用率高
  2. 不需要按顺序回收,回收速度快
  3. 新的对象分配速度也较快

缺点:存活对象移动需要额外时间与计算资源。标记整理对标记清除进行了改进和优化,应用更加广泛。

分代回收

分代回收(Generational Garbage Collection)是 JS 垃圾回收机制中的重要优化,是现代JavaScript引擎中采用的一种高效垃圾回收策略,它可以显著提高应用的性能和内存管理效率

✴️ 基本思想:

将内存中的对象分为新生代和老生代,根据对象的生命周期不同进行不同策略的回收。在分代回收的策略中,新创建的对象首先分配到新生代。垃圾回收器在新生代执行频繁的小规模垃圾回收,通常采用快速而简单的算法(比如Scavenge算法)来清理不再使用的对象。当对象在新生代经历了一定次数的垃圾回收仍然存活时,它们将被晋升到老生代。老生代的垃圾回收较为复杂,可能会涉及更多的算法和更大的回收范围。

优点:

通过区分不同对象的生命周期,它可以更精确地选择垃圾回收的时机和策略。大多数对象在创建后很快就变得不可访问,因此将它们分配到新生代,并频繁进行小规模的垃圾回收,可以有效地释放短期存活对象的内存。而那些长期存活的对象,由于它们在老生代,可能需要较长时间才会进行垃圾回收,从而避免频繁地进行大规模回收,提高了性能。

缺点:

  1. 实现复杂度高:分代回收需要维护对象年龄和代的信息,不同代采用不同算法,实现比较复杂。
  2. 内存开销大:记录额外的代信息需要占用内存资源。
  3. 参数依赖性强:分代大小、对象晋升年龄等参数的设置对效果有很大影响。
  4. 问题未完全解决:分代回收只是缓解了频繁回收问题,对象生命周期不固定,仍需全堆回收。
  5. 会产生内存碎片:分代独立会导致新生代频繁回收产生大量碎片。

空闲时垃圾回收

空闲时垃圾回收(Idle-time Garbage Collection)是JS垃圾回收机制的一种常见优化技术,用于在系统空闲或闲置时进行垃圾回收,以减少对应用性能的影响。这种技术旨在在用户不活跃或浏览器空闲时,利用系统资源进行垃圾回收,而不会对正在运行的应用产生明显的影响。

✴️ 基本思想:

  1. JS引擎监测代码执行情况,判断程序何时进入空闲状态。
  2. 在代码空闲执行期间启动垃圾回收,利用CPU空闲资源。
  3. 代码执行需要CPU时暂停垃圾回收,切换资源服务代码运行。
  4. 交替进行回收和执行,将回收工作分散到不同空隙中。

优点:

  1. 减少垃圾回收过程对代码执行流程的影响和干扰。
  2. 避免垃圾回收时产生长时间的执行停顿。
  3. 提高用户体验,减少垃圾回收造成的卡顿感。

缺点:空闲时垃圾回收并不意味着永远不会有任何垃圾回收暂停。在某些情况下,垃圾回收器可能仍需要在运行时进行一些必要的回收操作。空闲时垃圾回收只是在合适的时机尽量减少对应用的影响。

总结

垃圾回收是JS自动内存管理非常重要的一部分,可以自动回收不再需要的内存对象,防止内存泄漏。主要的垃圾回收算法包括标记清除,标记整理,引用计数等。现代浏览器一般combine多个算法来实现。为了优化效率,会使用分代回收适当区分新生和老生对象,以及闲时回收利用CPU空闲资源。垃圾回收会有一定的性能影响,我们应该编写高质量代码来配合,减少不必要的内存占用。

总的来说,垃圾回收机制使得JS开发人员不再需要关注内存控制这块复杂的工作。它极大地简化了JS的内存管理,使开发人员可以专注于业务代码的实现。这是JS作为一门优秀动态语言的重要支撑。

相关文章
|
1月前
|
JavaScript 前端开发 算法
JS垃圾回收
【10月更文挑战第30天】JavaScript 的垃圾回收机制是保证程序稳定运行的重要组成部分。了解垃圾回收的原理和算法,以及注意避免内存泄漏的问题,可以帮助开发者更好地利用 JavaScript 进行高效的开发
|
1月前
|
存储 JavaScript 前端开发
JavaScript的垃圾回收机制
【10月更文挑战第29天】JavaScript的垃圾回收机制是确保程序高效运行的重要保障,了解其工作原理和相关注意事项,有助于开发者更好地编写高性能、稳定的JavaScript代码。
|
2月前
|
存储 前端开发 JavaScript
JavaScript垃圾回收机制深度解析
【10月更文挑战第21】JavaScript垃圾回收机制深度解析
113 59
|
2月前
|
存储 JavaScript 前端开发
JavaScript垃圾回收机制与优化
【10月更文挑战第21】JavaScript垃圾回收机制与优化
38 5
|
3月前
|
JavaScript 前端开发 Java
JavaScript基础知识-垃圾回收
关于JavaScript垃圾回收基础知识的介绍。
39 1
JavaScript基础知识-垃圾回收
|
4月前
|
前端开发 JavaScript Java
揭开 JavaScript 垃圾回收的秘密——一场与内存泄漏的生死较量,让你的代码从此焕然一新!
【8月更文挑战第23天】本文通过多个实例深入探讨了JavaScript中的垃圾回收机制及其对应用性能的影响。首先介绍了基本的内存管理方式,随后分析了变量不再使用时的回收过程。接着,通过事件监听器未被移除及全局变量管理不当等场景展示了常见的内存泄漏问题。最后,文章介绍了使用`WeakRef`和`FinalizationRegistry`等现代API来有效避免内存泄漏的方法。理解并运用这些技术能显著提升Web应用的稳定性和效率。
97 0
|
5月前
|
存储 JavaScript 算法
你真的了解JS垃圾回收机制吗?
你真的了解JS垃圾回收机制吗?
33 0
|
5月前
|
自然语言处理 前端开发 JavaScript
前端 JS 经典:闭包与内存泄漏、垃圾回收
前端 JS 经典:闭包与内存泄漏、垃圾回收
50 0
|
6月前
|
JavaScript 前端开发 算法
JavaScript 使用自动垃圾回收机制来管理内存
JavaScript 使用自动垃圾回收机制来管理内存
37 0
|
存储 JavaScript 前端开发
JS进阶(三) 闭包,作用域链,垃圾回收,内存泄露
闭包,作用域链,垃圾回收,内存泄露 1、函数创建 创建函数 1、开辟一个堆内存(16进制的内存地址) 2、声明当前函数的作用域(再哪个上下文创建的,它的作用域就是谁) 3、把函数体内的代码当作字符串存储在堆内存当中(所以不执行没有意义) 4、把函数的堆内存地址类似对象一样放到栈中供对象调用 执行函数 1、会形成一个全新的私有上下文(目的是供函数中的代码执行),然后进栈执行 2、在私有上下文中有一个存放私有变量的变量对象 AO(xx) 3、在代码执行之前要做的事情 - 初始化它的作用域链<自己的上下文,函数的作用域> - 初始化this (箭头函数没有this) - 初始化Arguments实参
105 0