JavaScript的内存管理与垃圾回收

news2024/12/22 23:51:57

前言

JavaScript提供了高效的内存管理机制,它的垃圾回收功能是自动的。在我们创建新对象、函数、原始类型和变量时,所有这些编程元素都会占用内存。那么JavaScript是如何管理这些元素并在它们不再使用时清理它们的呢?

在本节中,我们将讨论JavaScript中的内存管理的所有内部过程。我们将关注以下主题:

  • JavaScript中的内存生命周期

  • JavaScript中的内存分配

  • 当内存不再使用时的释放

  • JavaScript的垃圾回收

  • JavaScript垃圾回收中的引用概念

  • 标记-清除算法

其他编程语言(例如C语言)支持手动内存管理方法,比如malloc()和free()。相比之下,JavaScript会自动为新对象分配内存,并在不再使用时释放它们。因此,JavaScript是web上速度最快的编程语言之一。

让我们了解JavaScript中的内存管理,深入探讨JavaScript垃圾回收的主题。

内存生命周期

每种编程语言的内存生命周期都是相似的。它按照以下方式工作:

  1. 为新实例分配所需的内存

  2. 在调用实例时使用已分配的内存(读取、写入)

  3. 当实例不再使用时,释放分配的内存

但是,第二部分可能在所有编程语言中有所不同,具体取决于它们的架构和用法。在低级别的语言(例如C语言)中,第一步和第三步是明确的,但在高级语言(例如JavaScript)中,它们通常是隐式的。

JavaScript中的内存分配

值的初始化

在JavaScript中,当值被声明时,它们会自动分配内存。我们不需要手动为创建的变量和对象分配内存。

让我们看下面的例子,解释不同类型的变量和对象的内存分配:


const a = 200; // 为一个数字类型的变量分配内存
const a = 'anystring'; // 为一个字符串类型的变量分配内存

const new_obj = {
 a: 5,
 b: null,
}; // 为一个对象分配内存并包含值

// 就像对象一样,它也会为数组和包含的值分配内存
const my_array = [5, null, 'anystring'];
// JavaScript也会为函数分配内存
function my_function(a) {
 return a + 5;
} // 它会分配一个函数(可调用对象)

// JavaScript也会为函数表达式对象分配内存
anyHtmlElement.addEventListener('click', function() {
 htmlElement.style.backgroundColor = 'green';
}, true);

从上面的例子中,我们可以看到JavaScript为每个对象、变量和方法分配了值。

读取和写入值

当在JavaScript中使用值时,可以从分配的内存中读取和写入这些值。我们可以从变量或对象属性的分配内存中读取和写入值,甚至可以将参数传递给函数。

在Javascript中,基本数据类型存储在栈内存中,引用数据类型存储在堆内存中,但是引用数据类型会在栈内存中存储一个实际对象的引用。
示例如下:

当内存不再使用时的释放

当需要释放分配的内存时,大多数内存管理问题都发生在这个阶段。在这个阶段,最复杂的任务是确定特定变量、对象或函数何时不再需要。

低级别的编程语言要求开发人员手动指定他们希望释放的分配实例的内存的时间点。

但是,像JavaScript这样的高级语言使用自动内存管理,也就是垃圾回收。

垃圾回收的主要用途是自动监视并找到不再使用的特定块,并将其回收。

然而,内存管理的自动处理完全基于近似值。因此,确定特定内存块是否仍然需要是无法确定的通用问题。这就是JavaScript垃圾回收器登场并高效地管理内存的原因。

现在我们已经讨论了JavaScript中的内存管理,让我们继续我们的主题:垃圾回收。

JavaScript垃圾回收

如前所述,垃圾回收面临的主要问题是自动查找特定内存是否将被使用。在这种情况下,垃圾回收器为解决这个问题实施了一种限制。为了解决这个问题,JavaScript使用了引用的概念。让我们了解引用的概念:

JavaScript垃圾回收中的引用概念

对于自动内存管理,JavaScript依赖于引用的概念。在这个概念中,在内存管理上下文中,如果另一个对象在应用程序中的后续位置被访问(无论是隐式还是显式访问),则将一个对象视为对另一个对象的引用。例如,对象将具有对其原型的引用,这是一个隐式引用,以及对其属性值的引用,这是一个显式引用。

在这种情况下,对象的概念将扩展到比JavaScript中的常规对象更广泛的范围。它还包含了对象的作用域。

引用计数垃圾回收

JavaScript的垃圾回收器遵循计算引用的算法。该算法确定一个对象是否还需要,以确定一个对象是否仍然有其他对象引用它。如果一个对象没有引用指向它,它将被视为垃圾。

让我们通过下面的例子来理解它:


let a = {
 x: {
  y: 5
 }
};
// 这里创建了两个对象:x和y,其中x被另一个y作为其属性之一引用。
// 另一个对象是通过分配给'a'变量来引用的。
// 在这里,给定的对象没有一个可以垃圾回收的理由。

let b = a; // 'b'变量是引用对象的第二个引用。
a = 5; // 现在,原来在'a'中的对象将有一个唯一的引用,即'b'变量。
let z = b.x; // 对象的'x'属性的引用。
// 现在,'z'对象将有两个引用:一个作为属性,另一个作为'z'变量。
b = 'chrome'; // 原来在'b'中的对象现在没有引用指向它。
// 然而它的'a'属性仍然被'z'变量引用,所以它不能被释放。
z = null; // 原来在'a'中的对象的'a'属性现在没有引用指向它。它可以被垃圾回收。

限制(循环引用)

JavaScript的垃圾回收器在发现循环引用时存在限制。例如,考虑以下例子:


function myFunction() {
 const a = {};
 const b = {};
 a.x = b;    // a引用了b
 b.y = a;    // b引用了a
 return 'someString';
}
myFunction();

从上面的例子中,我们创建了两个相互引用的对象,形成了一个循环引用的情况。当函数myFunction()完成执行时,它将超出作用域。它们在那时不再需要,但是它们分配的内存将被回收。因此,垃圾收集器不会清理它们的分配。

然而,垃圾收集器的引用计数算法将不会将它们识别为可回收对象,但它们中的每一个都有一个引用,这就是为什么它们都不会被标记为垃圾回收的原因。循环引用问题是应用程序中内存泄漏的原因之一。

IE 6和IE 7维护引用计数垃圾回收器,但没有其他现代引擎使用引用计数垃圾回收器来防止由于循环引用而引发的内存泄漏。

垃圾回收的两种常用方法

标记-清除算法

标记-清除算法减少了“一个对象不再使用”算法的使用,而是专注于“一个对象不可达”方法。

这种方法专注于根(一组对象)。在JavaScript中,根是全局对象。在这种方法中,垃圾收集器从根(全局对象)开始,找到从这些根指向的对象。它引用了从这些根对象指向的所有对象。因此,它找到了所有可达和不可达的对象。

这个算法是引用计数算法的改进。它将一个对象视为不可达,如果它没有引用指向它。因此,在这个算法中,如果对象具有零引用,那么它就不可达。所以,在这个算法中,如果对象具有循环引用,该对象不再持有真实的零引用。

截至2012年,所有现代浏览器都遵循标记-清除算法进行垃圾回收。过去几年中,JavaScript的所有改进都采用了这种方法,而不是其本地方法“一个对象不再需要”。

在第一个例子中,在函数调用后没有两个对象引用任何资源。因此,垃圾收集器将它们视为不可达的,并且回收它们的内存。

这是目前浏览器大多基于标记清除法。我们可以分为两个阶段:

  • 标记:从根节点遍历为每个可以访问到的对象都打上一个标记,表示该对象可达。
  • 清除:在没有可用分块时,对堆内存遍历,若没有被标记为可达对象就将其回收。

优点:实现简单。
缺点:a 内存过于碎片化。
   b 内存分配速度慢。
解决方法:标记-整理法
标记整理法和标记清除法标记阶段一致,只是整理阶段是先将被引用的对象移动到一端,然后清理掉标记的内存。

视图如下:

2、引用计数法

引用计数法就是追踪每个变量被引用的次数,当引用数为0将可以被回收掉。
优点:当引用数为0时会被立即回收
缺点:a 计数器的增减处理频繁,会导致空间的使用效率降低。
   b 循环引用无法收回,导致内存泄漏。
  若有一函数Person中a引用了b,b引用了a。每次调用函数Person,它们的引用计数都不为0,则永远不能被回收。
示例如下:

function Person(){
	let a={};
	let b={};
	a.prop = b;
	b.prop = a;
}

限制:

在应用程序中可能会有这样的时刻,手动指定哪些内存将被释放会很方便。要释放对象的内存,我们需要手动将其显式设置为不可达。

我们不能手动触发JavaScript垃圾收集器。

分代式垃圾回收机制

把分代式垃圾回收机制单独拎出来是因为涉及内容较多,容易混淆。v8中将内存分成了两个区,新生代和老生代。新生代对象存活时间较短,内存通常支持1~8MB。而老生代存储存活时间较长或常驻内存的对象。对于新老两块内存区域的垃圾回收频率不同,所以V8 采用了两个垃圾回收器来管控。
视图如下:


1、 新生代垃圾回收

新生代垃圾回收通过Scavenge策略进行垃圾回收,在具体实现中,主要采用了一种复制式的方法Cheney算法。Cheney算法将堆内存也分为两个区,一个使用状态的空间我们称为使用区。一个处于闲置状态的空间称为空闲区。新加入的对象都会被存放到使用区,当使用区快被写满时,就执行一次垃圾回收操作。

(1)垃圾回收流程

先对使用区中的活动做标记
标记完成后,将使用区的活动对象复制进空闲区并进行排序
将原先使用区对象占用的空间释放
最后进行角色互换,把空闲区变为使用区,使用区变为空闲区

(2)新生代对象何时会到老生代?

第一种情况:经过多次复制后依然存活的对象会被认为是生命周期较强的对象,会被移到老生代管理。
第二种情况:如果复制一个对象到空闲区时,空闲区空间占用超过25%,那么这个对象将被移到老生代区域。原因是,当完成Scavenge回收后,空闲区转变成使用区,需继续进行内存分配,若占比过大,将会影响后续内存的分配。

(3)并行回收

Javascript是一门单线程语言,它是运行在主线程上的,而在进行垃圾回收的时候就会阻塞Javascript脚本的执行,需等待垃圾回收完毕后再恢复脚本执行,这种行为叫全停顿。那当GC时间过长就会造成页面卡顿问题。那一个人干活慢,n个人一起速度便会是一个人的n倍。程序也一样,我们可以通过引入多个辅助线程来同时处理。因此V8引入了并行回收机制。
新生代对象空间就采用并行策略。在垃圾回收过程中,启动多个线程来负责新生代中的垃圾清理,这些线程同时将对象空间中的数据移到空闲区。由于这个过程中数据地址会发生改变,所以还需要同步更新引用这些对象的指针。

2、老生代垃圾回收

老生代数据大多是存活的对象,不需要时常清除更新,所以采用上面提到的标记清除法来进行垃圾回收。因为上面也提到标记清除后会产生大量内存碎片,所以V8就采用了上文所说的标记整理法来解决这个问题。

(1)增量标记

并行策略虽然可以增加垃圾回收的效率,对于新生代这样存放较小对象的回收器能有很好的优化,但其实还是全停顿式的。对于存放较大对象的老生代来说,这些较大对象GC时哪怕使用并行策略依旧会消耗大量时间。所以V8对老生代进行了优化,从全停顿标记切换到了增量标记。
增量标记:就是将一次GC分成多步小GC标记,让JS和小GC标记交替执行,直到标记完成。
问题来了,小GC标记执行完后是如何暂停执行JS任务,而后又是如何进行下一次小GC 标记?如果执行JS任务时刚被标记好的对象引用又被修改了该当如何?V8解决这两个问题的方法分别是三色标记法和写屏障。

1)解决问题一:三色标记法

标记清理法区分是通过非黑即白的策略,但这样便会出现在增量标记时,内存中黑白都有,我们无法区分下一步是什么?所以采用了三色标记法,使用每个对象的两个标记位和一个标记工作表来实现标记,两个标记位编码三种颜色:黑(11),白(00),灰(10)。

黑色表示对象自身及对象的引用都被标记(已检查状态)
白色表示未被标记的对象(初始状态)
灰色表示自身被标记,自身的引用未被标记(待检查状态)
执行流程如下:

初始将所有对象都是白色
从root对象开始,先将root对象标记为灰色并推入标记工作表中
当收集器从标记工作表中弹出对象并访问他的所有引用对象时,自身灰色就会变成黑色。
将自身的下一个引用对象标记为灰色
一直这样执行下去,直到没有可以被标记为灰色的对象时,剩下的白色对象都是不可达的,进入清理阶段。恢复时从灰色标记对象开始执行。

2)解决问题二:写屏障

为了解决黑色对象在程序执行中被新添加引用或已经标记黑色的被引用对象不再被引用了。写屏障就有了以下两个变化:

不对已标记的黑色对象做处理,因为在之后的GC中也会被清理。
Write-barrier 机制强制不变黑的对象指向白色对象。这个也被称作强三色不变性。所以一旦有黑色对象引用白色对象,该机制会强制将引用的白色对象改为灰色,从而保证下一次增量 GC 标记阶段可以正确标记。

(2)懒性清理

增量标记完后,如果当前内存足以支持代码的快速运行,也没必要立即清理,可让程序先运行,也无需一次性清理完所有垃圾对象,可以按需清理直到所有垃圾对象清理完后再继续增量标记。

并发回收

并发主要发生在工作线程上。当在工作线程(辅助线程)执行GC是,应用程序可以继续在主线程运行并不会被挂起。
这也是有问题的,因为GC也在进行,应用程序也在执行,此时对象的引用关系随时都有可能变化,所以之前做的一些标记就需要改变,所以需要读写锁机制来控制这一点。

总结:

JavaScript提供了高效的内存管理机制。JavaScript自动支持内存管理,对我们来说是透明的。我们不能手动触发JavaScript垃圾收集器。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1466085.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LED景观照明灯驱动电路串联、并联和恒流3款方案

LED景观照明灯是现代城市照明中常见的一种灯具。为了保证LED景观照明灯的正常工作,需要设计合适的驱动电路。LED景观照明灯的驱动电路可以采用串联、并联或恒流的方式来设计。 首先,串联驱动电路是指将多个LED灯串联在一起,然后接入电源进行…

不做内容引流,你凭什么在互联网上赚钱?

孩子们放寒假了,待在家里不是看电视,就是拿着手机刷视频,脸上是各种欢快和满足。只是一切换到写作业模式,孩子是各种痛苦表情包,家长则是使出浑身解数,上演亲子大战。可见娱乐常常让人愉悦,而学…

MyBatis-Plus框架(千峰学习笔记)

简介 MyBatis-Plus是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,主要作用 为简化开发、提高效率。 我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。 特性 ● …

XFF伪造 [MRCTF2020]PYWebsite1

打开题目 直接查看源码 看到一个./flag.php 访问一下 购买者的ip已经被记录,本地可以看到flag,那么使用xff或者client-ip伪造一下ip试试 bp抓包 加一个X-Forwarded-For头 得到flag

无线局域网(WLAN)简单概述

无线局域网 无线局域网概述 无限局域网(Wireless Local Area Network,WLAN)是一种短距离无线通信组网技术,它是以无线信道为传输媒质构成的计算机网络,通过无线电传播技术来实现在空间传输数据。 WLAN是传输范围在1…

2、Web攻防-SQL注入-联合查询注入

用途:个人学习笔记,有所借鉴,欢迎指正! 声明:只用于学习交流,点到为止,请勿非法测试。 概念: 联合查询注入:联合注入是回显注入的一种,也就是说联合注入的前…

关于Android下gralloc,hwcompoer以及surface模块的重新认识

关于Android下gralloc,hwcompoer以及surface模块的重新认识 引言 欠债还钱天经地义,知识的债也是如此!这不必须得将我前面欠下来的债给补上!对于任何复杂的知识点,我们都可以采用庖丁解牛的学习方式,一步步…

DSL Query基本语法

DSL Query基本语法 查询的基本语法如下: GET /indexName/_search {"query":{"查询类型":{"查询条件":"条件值"}} }查询所有 GET /indexName/_search {"query":{"match_all":{}} }match查询&#xf…

5分钟让你搞懂什么是Http协议

计算机网络基础课程是计算机专业方向非常重要的一门功课。 所有的互联网都通过网络协议来建立通信连接。 而http协议又是一种无状态的协议,也是工作中最常用的一种基于Web浏览器的网络通信协议。 如何学习http协议?提供三种方法供参考: 第…

分类预测 | Matlab实现KPCA-ISSA-LSSVM基于核主成分分析和改进的麻雀搜索算法优化最小二乘支持向量机故障诊断分类预测

分类预测 | Matlab实现KPCA-ISSA-LSSVM基于核主成分分析和改进的麻雀搜索算法优化最小二乘支持向量机故障诊断分类预测 目录 分类预测 | Matlab实现KPCA-ISSA-LSSVM基于核主成分分析和改进的麻雀搜索算法优化最小二乘支持向量机故障诊断分类预测分类效果基本描述程序设计参考资…

4.网络游戏逆向分析与漏洞攻防-游戏启动流程漏洞-模拟游戏登陆器启动游戏并且完成注入

内容参考于:易道云信息技术研究院VIP课 上一个内容:游戏启动流程的分析 码云地址(master 分支):https://gitee.com/dye_your_fingers/titan 码云版本号:bcf7559184863febdcad819e48aaacad9f25d633 代码下…

第三百六十二回

文章目录 1. 概念介绍2. 使用方法3. 代码与效果3.1 示例代码3.2 运行效果 4. 内容总结 我们在上一章回中介绍了"如何创建垂直方向的Switch"相关的内容,本章回中将介绍SlideSwitch组件.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 我们在…

在ubuntu20.04 上配置 qemu/kvm linux kernel调试环境

一:安装qemu/kvm 和 virsh qemu/kvm 是虚拟机软件,virsh是管理虚拟机的命令行工具,可以使用virsh创建,编辑,启动,停止,删除虚拟机。 (1):安装之前&#xff0c…

九州金榜|家庭教育小技巧,孩子好习惯养成记

家庭教育对于孩子的发展至关重要,家长一定要重视孩子在家里的举动,要及时纠正孩子的不足,发展孩子的优良品德和教孩子养成勤俭朴素的的好习惯。九州金榜家庭教育将从以下方面说一下家庭教育中的方法技巧。 一、家长以身作则 家长教育孩子&a…

js设计模式:策略模式

作用: 根据不同的条件去进行相应的业务逻辑处理 就好比针对每种情况都制定对应的方案,触发条件就启动某项方案策略 示例: //策略对象const arrangeFun {model1:(value1,value2,value3,value4)>{return ${value1}${value2}${value3}:${value4}},model2:(value1,value2,va…

【Postgres】11、PROCEDURE 存储过程、FUNCTION 函数、使用方式和区别

文章目录 一、PROCEDURE1.1 语法1.2 描述1.3 参数1.4 示例 二、FUNCTION2.1 语法2.2 重载2.3 示例2.4 兼容性2.5 示例2.5.1 declare variable 定义变量2.5.2 declare、ARRAY、ANY2.5.2.1 ARRAY 和 ANY 三、其他3.1 PL/pgSQL 在PostgreSQL中,存储过程(Pro…

Nginx跳转模块之rewrite

一.location与rewrite模块的区别 rewrite:对访问的域名或者域名内的URL路径地址重写 location:对访问的路径做访问控制或者代理转发 二.rewrite模块基本内容 1.功能 通过正则表达式的匹配来改变URI,可以同时存在一个或多个指令&#xff0c…

前端工程Bem架构及其封装

文章目录 简介语法在vue3项目中引用sass创建bem.scss文件修改vite.config.tsvue文件中使用结果 这是我学习记录的笔记,如有不正,欢迎补充 简介 首先认识一下什么是bem架构?BEM的意思就是块(block)、元素(e…

学校档案室管理制度内容

学校档案室管理制度是指对学校档案室进行管理的规定和流程。以下是一个示例的学校档案室管理制度的内容: 1.档案室的管理部门和责任人员: 学校档案室由学校行政部门负责管理,行政部门指定专门的档案管理员负责档案室的日常管理工作。 2.档案室…

ChatGPT在数据处理中的应用

ChatGPT在数据处理中的应用 今天的这篇文章,让我不断体会AI的强大,愿人类社会在AI的助力下走向更加灿烂辉煌的明天。 扫描下面二维码注册 ​ 数据处理是贯穿整个数据分析过程的关键步骤,主要是对数据进行各种操作,以达到最终的…