从引擎到垃圾回收器:JavaScript内存管理全方位解析(一)

简介: 从引擎到垃圾回收器:JavaScript内存管理全方位解析

I. 前言

简介

JavaScript是一种广泛应用于浏览器和服务器端的脚本语言垃圾回收是它的一个重要特性

JavaScript垃圾回收机制通过标记和清除等算法来管理内存,避免内存泄漏和内存溢出,保证代码的性能和稳定性。

JS垃圾回收的指导原则

JavaScript垃圾回收的指导原则包括以下几点:

  1. 内存自动分配JavaScript引擎通过垃圾回收器自动分配内存,程序员无需手动管理内存。
  2. 垃圾自动回收JavaScript垃圾回收器会自动识别不再使用的对象,并回收这些垃圾。程序员不需要手动释放内存。
  3. 对象的引用计数JavaScript垃圾回收器使用引用计数算法来检测和回收不再使用的对象。
  4. 循环引用的处理:一旦对象之间形成了循环引用,就不再能够被直接回收。垃圾回收器使用标记-清除算法来检测和处理这种情况。
  5. 分代回收JavaScript垃圾回收器将堆分为多个代,每个代有自己的回收策略。这种分代回收的策略可以充分利用对象的生命周期特征,提高回收效率。

这些指导原则可以帮助程序员更好地理解JavaScript内存管理和垃圾回收的基础原理,从而更好地编写高效、稳定的JavaScript代码。

本文的内容

本文将深入解析JavaScript垃圾回收机制的原理、技术和优化方案,并通过案例应用剖析Vue和React中的垃圾回收技术。

II. JS内存管理

JS引擎

JavaScript引擎是一种解释器或编译器,它将JavaScript代码解析并转换为可执行的机器代码

JavaScript引擎的主要任务是解析JavaScript代码并将其转换为计算机可以理解的指令。当一个网页在浏览器中被打开时,JavaScript引擎会解释和执行网页中的JavaScript代码,以便生成互动式的用户界面和丰富的用户体验。

常见的JavaScript引擎包括

  • V8引擎(Chrome和Node.js使用)
  • SpiderMonkey引擎(Firefox使用)
  • JavaScriptCore引擎(Safari使用)

这些引擎都有自己的优点和特点,在性能、兼容性和可扩展性等方面有所不同。

内存管理

内存管理是指计算机系统中负责管理程序运行时所需内存的一组活动。在JavaScript中,内存管理是由JavaScript引擎自动执行的,程序员无需手动管理内存。通过垃圾回收机制,JavaScript引擎可以自动识别不再使用的内存,并释放其占用的空间。

JavaScript中的垃圾回收机制分为两种:标记清除和引用计数

  • 标记清除是指在程序中使用的变量被标记,一段时间后未被使用的变量会被清除;
  • 引用计数是一种计数方法,即对每个变量进行加1或减1的计数,当计数为0时,该变量就会被清除。

在编写大型JavaScript程序时,应注意内存泄漏问题。内存泄漏是指程序中有一些不被使用的内存仍然被占用的情况。这可能是因为变量未正确删除或未释放内存。一旦内存泄漏发生,将会导致程序运行速度变慢,甚至程序崩溃。因此,开发者应该在编写程序时注意内存管理,以避免内存泄漏的出现。

内存中的变量

在JavaScript中,变量被存储在内存中,并且通过变量名来引用。在计算机中,内存被分为多个变量存储空间(也称为内存单元),每个变量存储空间都有一个唯一的地址。

JavaScript中的变量可以被存储在栈内存和堆内存中。

  • 栈内存是指内存中存储数据的一种结构,它具有"先入后出"的数据结构特点,用于存储基本类型的数据,例如number、string、boolean等。
  • 堆内存是指用于存储复杂的对象(object)和数组(array)等数据类型。

在JavaScript中,变量的存储方式和生命周期受到作用域的影响。当申明一个变量,该变量会被存储在内存中,当该变量不再使用时,JavaScript引擎将自动从内存中删除变量。变量的生命周期是指在一个程序中,从变量创建到被销毁的整个过程。

变量的作用域是指在一个JavaScript程序中,变量的可见范围。在JavaScript中,采用词法作用域(静态作用域),即一旦一个变量被定义在函数里面,它在函数外面就不可见。而在函数内部定义的变量,在函数外部是不可见的。变量的作用域是JavaScript程序设计中的一个重要概念,可以让程序员更好地控制变量的使用和保护内存空间。

堆和栈

堆和栈都是计算机内存中的存储空间。它们之间有着重要的区别。

栈是一种内存区域,用于存储基本类型的变量和函数调用栈。在程序执行时,当调用一个函数时,该函数的所有参数和本地变量都被压入栈中,函数返回时,这些变量就被弹出栈,这种机制被称为"后进先出"(LIFO)。栈的大小是固定的,由操作系统预分配,一旦超过栈的大小,将会导致栈溢出错误。

堆是一种内存区域,用于存储复杂数据类型和动态分配的内存块。在堆内存中,数据存储在不连续的地址上,其大小也不是固定的。在JavaScript中,对象和数组都存储在堆中,它们的大小取决于其成员变量的数量和大小。JavaScript通过垃圾回收器来管理堆内存,并定期扫描堆中的对象,标记那些不再使用的对象,并释放其内存空间以供重用。

在JavaScript中,函数调用时,函数局部变量被分配在栈中,并在函数执行完毕时被弹出,而对象和数组等复杂类型则是在堆中动态分配内存,并由JavaScript垃圾回收器自动回收。

总的来说,栈和堆在内存管理中扮演不同的角色。栈是用于存储临时数据和函数调用的局部变量,而堆则是用来存储复杂数据类型和动态分配的内存。

内存泄漏与内存溢出

内存泄漏和内存溢出都是指计算机内存管理问题,但是它们的产生原因和影响以及解决方法都不同。

内存泄漏是指程序在使用内存后未能释放内存,导致内存消耗过多而没有被回收重新利用。内存泄漏通常发生在开发者没有正确处理变量、对象或函数的生命周期和作用域等问题。例如,不再使用的对象未被正确地解除引用,并继续被程序保留,使得内存无法被垃圾回收器回收,这可能导致程序逐渐消耗更多的内存资源,并最终导致系统崩溃或变得非常缓慢。

内存溢出是指内存中已经没有足够的空间来存储请求的信息。通常是某个程序申请的内存空间超过系统可用的物理内存或虚拟内存的大小,导致程序停止运行或异常退出。内存溢出可能是程序中的一个逻辑错误或者是申请内存资源时未正确检查内存大小的问题。

解决内存泄漏的方法是在开发中正确管理变量、对象和函数的生命周期和作用域,及时清除不再需要的内存对象。解决内存溢出的方法则通常是优化代码,减少内存的使用,或者增加物理内存或虚拟内存的大小以满足程序的需求。同时,程序员应该定时使用内存检查工具来找出并解决可能的内存泄漏和内存溢出问题。

III. JS垃圾回收机制

定义垃圾

在计算机科学中,内存中的垃圾指的是无法通过任何现有的引用路径访问的内存对象。也就是说,这些对象已经不再被程序所使用,但由于存在引用,不能被垃圾回收器自动释放,造成内存资源的浪费。

垃圾回收是一种机制,用于回收内存中已经不再使用的对象空间,以便可以将这部分内存重新使用。当一个对象在内存中没有任何引用时,垃圾回收机制会自动将其标记为垃圾,然后回收该对象所占用的内存空间。

在JavaScript语言中,垃圾回收机制是非常重要的,因为JavaScript是一门动态语言,程序员无法手动管理内存,必须依赖引擎的垃圾回收机制。JavaScript引擎会自动执行垃圾回收来回收不再使用的对象占用的内存空间,以便将其释放给其他对象使用,从而使内存资源得到优化。

在编写JavaScript程序时,应当注意内存泄漏的问题。内存泄漏指的是程序未经妥善处理,将对象保留在内存中,从而导致内存空间用尽而无法被垃圾回收器回收利用。引起内存泄漏的原因种类繁多,但如果程序员理解了JavaScript内存管理模型和垃圾回收机制的实现原理,可以有效地避免内存泄漏。

垃圾回收的实现

垃圾回收是一种动态内存管理的方式,其主要目的是回收不再使用的内存空间,为了实现高效和准确的内存回收,不同的编程语言采用了不同的垃圾回收算法和实现方式。

在JavaScript中,垃圾回收机制的实现方式主要包括引用计数和标记清除两种算法。

1. 引用计数算法

引用计数是最早的垃圾回收算法之一,它的工作原理是跟踪每个对象被引用的次数。当某个对象的引用计数为0时,即没有任何引用指向它,垃圾回收机制就可以回收这个对象的内存空间。

引用计数算法的优点在于实现简单,对程序的执行速度几乎没有影响。但该算法存在循环引用等问题,即两个或多个对象相互引用,无法被垃圾回收机制识别和回收,可能导致内存泄漏的问题。

2. 标记清除算法

标记清除算法是当前JavaScript中使用的主要垃圾回收算法。其工作原理是从根节点对象(如全局对象和函数参数)开始查找对象,将所有可达的对象标记,在标记结束后,将未标记的所有对象回收空间。

标记清除算法的优点在于可以处理循环引用的情况,但当垃圾回收的频率越高,程序就越慢。因此,JavaScript引擎通常通过调整垃圾回收器的策略,来平衡内存管理和程序性能。

总的来说,垃圾回收是动态内存管理的一种重要手段,能够帮助程序员高效地管理内存资源,使程序更加稳定和高效。在编写JavaScript程序时,应该注意内存泄漏的问题,以确保程序的内存使用情况得到最优化的管理。

垃圾回收算法

垃圾回收算法是指垃圾收集器用来确定内存中哪些对象已经不再使用,可以回收内存空间的一种算法。

常见的垃圾回收算法有以下几种:

1. 引用计数算法

引用计数算法的核心是跟踪每个对象被引用的次数。当一个对象被新的引用指向时,其引用计数加1;当一个对象的引用被释放或删除时,其引用计数减1;当一个对象的引用计数为0时,就可以安全地回收该对象所占用的内存空间。该算法实现简单,但有可能存在循环引用的问题,导致内存泄漏。

2. 标记-清除算法

标记-清除算法是目前主流的垃圾回收算法之一,其核心是标记那些仍然被使用的对象,然后清除那些未被标记的对象。具体步骤如下:

1)从根节点对象开始,将所有可达的对象标记;

2)遍历整个对象集合,清除未被标记的对象;

3)清除后,回收所有被占用的内存空间,以便将其重用。

该算法通常能够处理多种复杂的内存结构,但会出现“停顿”现象,并影响程序性能。

3. 标记-压缩算法

标记-压缩算法是标记-清除算法的优化。它遵循标记-清除的基本思路,但在内存回收过程中会进行内存整理。具体步骤如下:

1)从根节点对象开始,将所有可达的对象标记;

2)整理内存空间,压缩对象,并将其移动到新的内存空间中;

3)移动完对象后,清除未标记的对象;

4)清除后,重复第二、三步,直到完成所有回收操作。

该算法能够更有效地使用内存,同时减少程序出现“停顿”的情况。

4. 分代收集算法

分代收集算法是指将内存中的对象划分为多个世代,并对不同世代的对象采用不同的垃圾回收策略。一般将新分配的对象视为“年轻代”,而将存活时间更长的对象视为“老年代”。在分代收集算法中,年轻代离垃圾回收器最近,垃圾回收器更频繁地清理年轻代,而老年代离垃圾回收器更远,回收调整的不太频繁。通过这种方式,可以最小化垃圾回收器的开销,提高程序性能和内存利用率。

以上是常见的几种垃圾回收算法,不同的算法有不同的优劣点,程序员需要根据实际应用需求和条件选择最合适的算法。

代际假说

代际假说(Generational Hypothesis)是指在大多数情况下,绝大部分对象在创建后只被使用很短的时间,称为"新生代";而只有少数对象被频繁使用,称为"老年代"。据此,将堆内存划分为不同的世代(Generation),对垃圾回收机制实现做出优化,提高效率。

代际假说的提出是基于观察到了一种现象,即绝大多数的对象只使用了很短时间(比如几个方法或者循环周期),而只有一小部分的对象是持久存在的。实践表明,将堆分为新生代和老年代,分别采用不同的垃圾回收算法,可以提升垃圾回收效率。

在实现过程中,一般将年轻代分为Eden区、From区、To区。对于新建的对象,首先分配到Eden区,并进行垃圾回收。在Eden区内部,使用复制算法回收不再使用的对象;如果生存周期足够长,就晋升到From区。当某个区域无法再分配对象时,就进行一次垃圾回收,清理出存活下来的对象,将它们复制到To区,然后清空From区和Eden区,继续进行分配。这样,在每次回收中,只需要处理新生代对象,而忽略老年代,可以实现极高的效率。

总的来说,代际假说与分代收集算法密不可分,对于内存管理和垃圾回收效率的提升起到了非常重要的作用。

分代回收

分代回收(Generational Garbage Collection)是基于代际假说的一种垃圾回收方法,将内存中的对象根据其生命周期长度划分为不同的代,采用不同的垃圾回收策略来达到最佳效果。

一般地,分代回收将堆内存分为新生代和老年代两个部分。

新生代一般包含三个子代,即Eden区、Survivor 0区和Survivor 1区。新创建的对象会被分配到Eden区,经过一定使用周期后,会被复制到Survivor 0区。在一定时间段后,将Survivor 0区中存活的对象复制到Survivor 1区,同时对Survivor 0区进行清空。在Survivor 1区满了之后,再次执行一次清除操作。在两个Survivor区域复制一定次数之后,内存中还存活的对象会被晋升到老年代中。

老年代相比于新生代,内存使用周期更长,其中的对象数量相对较少,也更少会被回收。所以,对于老年代内的对象,分代回收采用更为保守的策略。

分代回收通过在不同代别之间分配垃圾收集开销,优化了垃圾回收器的性能。由于新生代中大部分的对象都具有短暂生命周期,所以将垃圾收集机制专门针对新生代进行优化,也就可以提高程序整体的垃圾收集效率。分代回收是一种当今主流的垃圾回收策略,被广泛应用于JVM、JavaScript等多种编程语言和虚拟机中。

垃圾回收器

垃圾回收器是一种实现动态内存管理的程序,用于自动检测和回收不再使用的内存空间,避免内存泄漏和程序崩溃等问题。垃圾回收器常见于各种编程语言和虚拟机中,如Java虚拟机、JavaScript引擎等。

垃圾回收器的主要任务是找到哪些内存空间可以被回收,回收这些空间,并将其标记为未使用状态。通常,垃圾回收器会采用不同的垃圾回收算法和内存管理策略,以优化内存分配和回收,提高程序性能和稳定性。

常见的垃圾回收器包括:

1. 标记-清除垃圾回收器

标记-清除垃圾回收器是最简单和最常见的垃圾回收器之一。它的工作原理是从根节点对象开始,遍历整个对象集合,标记所有可达的对象,然后清除未被标记的对象,并回收已使用的内存空间。

2. 标记-整理垃圾回收器

标记-整理垃圾回收器是标记-清除垃圾回收器的改进版,其主要特点是在垃圾回收前会进行内存整理,以保证回收后的内存空间连续可用。

3. 复制式垃圾回收器

复制式垃圾回收器将内存空间分成两份,每次分配内存时,只使用其中的一份。当某个内存空间不再使用时,将其中仍然存活的对象复制到另一份空间中,然后清空原始空间的所有内容,使其重新可用。

4. 分代垃圾回收器

分代垃圾回收器是针对对象生命周期不同的情况进行优化的垃圾回收器。该垃圾回收器将堆内存分为多个世代,分别采用不同的垃圾回收策略,提高回收效率。

总的来说,垃圾回收器是现代编程语言和虚拟机中必不可少的一部分。在开发程序时,选择合适的垃圾回收器可以充分利用内存资源,并提高程序的性能和可靠性。

标记清除法

标记清除法(Mark-and-Sweep)是一种常见的垃圾回收算法。其主要思想是从根节点(即程序中显式或隐式使用的对象)开始,递归地搜索所有可达的对象,并标记这些对象为“已使用状态”。在搜索完成后,所有未被标记的内存区域都可以被回收。

下面是标记清除法的主要步骤:

  1. 标记阶段:从根节点开始遍历程序中的所有对象,标记所有可达的对象为“已使用状态”。
  2. 清除阶段:扫描整个内存区域,将所有未标记的对象和内存空间回收。

标记清除法的优点是实现简单,对于内存分配不连续的语言(如C语言)尤为有用。但它也有一些缺点,包括:

  1. 不连续分配内存会导致内存碎片化,降低整体内存利用率。
  2. 标记和清除的过程涉及到许多对象,会引起程序的“停顿”,降低程序性能
  3. 无法处理循环引用的情况,导致内存泄漏

总的来说,标记清除法是一种基本的垃圾回收算法,适用于简单的内存管理系统和内存分配不连续的语言。但在现代的编程语言和虚拟机中,一般会采用更高效和复杂的垃圾回收算法,以提高程序性能和内存利用率。

引用计数法

引用计数法是一种常见的垃圾回收算法,它基于对象的引用计数来判断对象是否可以被回收

每个对象都有一个引用计数器,表示当前有多少个指针指向该对象。

当对象的引用计数器为零时,就可以回收该对象。

引用计数法的主要优点是实现简单、实时性好,当一个指针指向一个对象时,引用计数器就加一,当指针不再指向该对象时,引用计数器就减一,不需要等待垃圾回收周期。

引用计数法的缺点是无法处理循环引用的情况。例如,当对象A引用对象B,对象B引用对象A时,它们的引用计数器永远无法变为零,导致内存泄漏。

为了解决循环引用导致的问题,一般都需要采用其他的垃圾回收算法,例如标记清除法、标记-整理法和复制式垃圾回收等。

总的来说,引用计数法是一种常见的垃圾回收算法,具有实现简单、实时性好等优点。但由于无法处理循环引用等情况,一般需要搭配其他的垃圾回收算法使用,以提高内存回收的效率和准确性。

增量收集法

增量收集法(Incremental Garbage Collection)是一种常见的垃圾回收算法,它采用分阶段垃圾回收的方式,使得回收过程可以在程序执行过程中分批完成,减少了程序的“停顿”时间,提高了程序的响应速度。

增量收集法的主要思想是在垃圾回收过程中让程序继续执行,同时将垃圾回收过程分成多个阶段,每个阶段回收部分垃圾,直到所有垃圾都被清除。这样,程序不需等待所有垃圾被清除之后再恢复执行,而是可以在垃圾回收过程中继续执行,减少了程序的停滞时间,提高了程序的响应速度。

增量收集法的主要优点是:

  1. 减少了程序的停滞时间,提高了程序的响应速度。
  2. 可以逐步清除垃圾,不需要等待所有垃圾被清除之后再恢复执行。
  3. 支持并发标记,可以在垃圾回收过程中允许程序继续执行,在多处理器系统中的效果尤为明显。

增量收集法的主要缺点是:

  1. 增加了垃圾回收器的实现复杂度。
  2. 增量回收的垃圾回收器不能更改指针及其它结构中内存的内容,以免干扰并发执行的程序的操作。

总的来说,增量收集法是一种常见的垃圾回收算法,可以提高程序的响应速度,减少程序的停滞时间。但它也有一些缺点,比如实现复杂度高、无法更改指针及其它结构中内存的内容等。在实际应用中,需权衡其优缺点,根据具体情况选择合适的垃圾回收算法。


从引擎到垃圾回收器:JavaScript内存管理全方位解析(二)https://developer.aliyun.com/article/1426350

相关文章
|
7天前
|
JavaScript 前端开发 搜索推荐
CSR、SSR与同构渲染全方位解析
CSR、SSR与同构渲染全方位解析
16 0
|
6天前
|
JavaScript 前端开发 算法
【JavaScript】JavaScript 垃圾回收机制深度解析:内存管理的艺术
JavaScript的内存管理和垃圾回收机制涉及栈内存与堆内存、引用计数与标记-清除算法。栈内存存储基本类型和函数调用时的局部变量,而堆内存用于复杂数据类型,如对象和数组。垃圾回收主要通过标记-清除策略,处理不再被引用的对象。现代引擎如V8使用分代收集和增量标记等优化方法,减少停顿并提升性能。开发者应注意避免内存泄漏,如及时解除引用、管理DOM引用和定时器,使用WeakMap和WeakSet等。理解这些原理和最佳实践对于编写高效代码至关重要。
18 5
|
9天前
|
存储 缓存 JavaScript
JavaScript内存泄漏通常发生在对象不再需要时
【6月更文挑战第16天】JavaScript内存泄漏常由闭包引起,当不再需要的对象仍被闭包引用时,垃圾回收机制无法清理。例如,创建返回大型对象引用的闭包函数会导致内存泄漏。避免泄漏需及时解除引用,清除事件监听器,利用WeakMap或WeakSet,以及定期清理缓存。使用性能分析工具监控内存使用也有助于检测和解决问题。
22 8
|
7天前
|
自然语言处理 JavaScript 前端开发
【JavaScript】JavaScript基础知识强化:变量提升、作用域逻辑及TDZ的全面解析
【JavaScript】JavaScript基础知识强化:变量提升、作用域逻辑及TDZ的全面解析
13 3
|
7天前
|
JavaScript 前端开发 Java
【JavaScript】ECMAS6(ES6)新特性概览(二):解构赋值、扩展与收集、class类全面解析
【JavaScript】ECMAS6(ES6)新特性概览(二):解构赋值、扩展与收集、class类全面解析
15 2
|
7天前
|
SQL 自然语言处理 前端开发
【JavaScript】ECMAS6(ES6)新特性概览(一):变量声明let与const、箭头函数、模板字面量全面解析
【JavaScript】ECMAS6(ES6)新特性概览(一):变量声明let与const、箭头函数、模板字面量全面解析
9 2
|
7天前
|
存储 JavaScript 前端开发
【JavaScript】JavaScript 中的 Class 类:全面解析
【JavaScript】JavaScript 中的 Class 类:全面解析
14 1
|
7天前
|
JavaScript 前端开发 数据处理
【JavaScript】JavaScript数组全方位探索指南:深入理解数组特性
【JavaScript】JavaScript数组全方位探索指南:深入理解数组特性
11 1
|
16天前
|
机器学习/深度学习 人工智能 自然语言处理
智能时代的引擎:深度学习技术解析
【6月更文挑战第8天】本文深入探讨了深度学习技术,一种基于人工神经网络的机器学习方法。我们将从其基本原理出发,分析其在数据处理、特征提取和模式识别方面的强大能力。文章将通过具体案例,展示深度学习在图像识别、自然语言处理等领域的应用,并讨论其面临的挑战与未来发展方向。
|
15天前
|
传感器 存储 SQL
ClickHouse(15)ClickHouse合并树MergeTree家族表引擎之GraphiteMergeTree详细解析
GraphiteMergeTree是ClickHouse用于优化Graphite数据存储和汇总的表引擎,适合需要瘦身和高效查询Graphite数据的开发者。它基于MergeTree,减少存储空间并提升查询效率。创建表时需包括Path、Time、Value和Version列。配置涉及pattern、regexp、function和retention,用于指定聚合函数和数据保留规则。文章还提供了建表语句示例和相关资源链接。
15 1

推荐镜像

更多