浏览器的垃圾回收机制

news/2025/1/7 2:09:11/文章来源:https://www.cnblogs.com/zimengxiyu/p/18652911

在前端开发中,内存管理是一个非常重要的领域。浏览器的垃圾回收机制(Garbage Collection, GC)是现代浏览器中的核心部分之一,它可以自动管理内存的分配与回收,帮助开发者避免手动管理内存,从而减少内存泄漏和性能问题的风险。

一、浏览器的垃圾回收机制

垃圾回收是一种自动内存管理机制,用于识别和释放不再使用的内存。垃圾回收的核心目标是释放不再使用的内存空间。在浏览器中,当页面中的某些对象不再被需要时,垃圾回收机制会自动将它们从内存中移除,确保不会发生内存泄漏。

JavaScript 是一种内存自动管理的语言,内存管理的核心就是垃圾回收。垃圾回收机制通过两种主要方式来检测和释放内存:

  1. 引用计数(Reference Counting):追踪每个对象的引用次数,当一个对象的引用次数为零时,就认为该对象不再使用,可以释放它占用的内存。
  2. 标记清除(Mark-and-Sweep):通过标记所有可达的对象,然后清除所有未标记的对象来回收内存。

引用计数曾经是早期垃圾回收机制中的一个重要方法,但由于其无法解决循环引用、性能开销较大以及不能有效解决内存碎片问题,现代浏览器和大多数编程语言已经转向使用更高效的垃圾回收策略,如 标记-清除算法分代回收增量回收并行回收 等。

1.引用计数

引用计数(Reference Counting)是一种较为基础的垃圾回收算法,它通过追踪对象的引用次数来判断对象是否可以被回收。每个对象都维护一个计数器,表示它被多少个其他对象引用。当一个对象的引用计数降为零时,表示没有任何引用指向该对象,可以安全地回收该对象的内存。

引用计数的工作原理

  1. 初始化引用计数:每当一个对象被创建时,它的引用计数会初始化为 1。当该对象被其他对象引用时,引用计数会增加;当该对象的引用被销毁或指向其他对象时,引用计数会减少。

  2. 对象引用计数增加与减少

    • 当一个对象被引用时(如赋值给其他变量或作为函数参数传递),它的引用计数增加。
    • 当一个引用被销毁时(如局部变量超出作用域或赋值为 null),该对象的引用计数减少。
  3. 回收垃圾对象:当一个对象的引用计数降到零时,意味着没有任何引用指向该对象,垃圾回收器会回收该对象占用的内存。

引用计数的示例

假设我们有以下 JavaScript 代码:

let obj1 = { name: "Object 1" };  // 引用计数为 1
let obj2 = obj1;                   // 引用计数为 2,因为 obj2 引用了 obj1
let obj3 = { ref: obj1 };          // 引用计数为 3,因为 obj3 引用了 obj1

obj2 = null;  // 引用计数为 2,obj1 的引用计数减少 1
obj3 = null;  // 引用计数为 1,obj1 的引用计数再次减少 1// 此时,obj1 的引用计数变为 0,垃圾回收器可以回收 obj1

在上述代码中,obj1 的初始引用计数为 1,后来通过 obj2obj3 增加了引用计数。当 obj2obj3 被设置为 null 时,obj1 的引用计数逐渐减为 0,垃圾回收器可以回收 obj1 占用的内存。

循环引用问题

引用计数的一个显著缺点是无法解决循环引用问题。例如,如果两个对象互相引用对方,它们的引用计数始终大于 0,即使它们不再被其他对象引用,垃圾回收器也无法将它们回收,从而导致内存泄漏。

示例:

function createCircularReference() {let obj1 = { name: "Object 1" };let obj2 = { name: "Object 2" };obj1.ref = obj2;  // obj1 引用 obj2obj2.ref = obj1;  // obj2 引用 obj1return [obj1, obj2];
}let circularObj = createCircularReference();
circularObj = null;  // 即使没有其他引用,obj1 和 obj2 依然互相引用,它们无法被回收

在这种情况下,即使 circularObj 被设置为 null,因为 obj1obj2 互相引用,引用计数不会降到零,因此它们不能被回收。

2. 标记-清除

标记-清除算法是现代垃圾回收器(Garbage Collector, GC)中常用的内存回收算法,广泛应用于 JavaScript、Java、Python 等编程语言的垃圾回收机制。它的核心思想是通过标记存活对象,并清除未标记的对象来实现内存的回收。

标记-清除算法的基本流程可以分为两个阶段:标记阶段(Mark Phase)清除阶段(Sweep Phase)

  1. 标记阶段(Mark Phase)

    • 在这个阶段,垃圾回收器会从根对象(Root Object)开始遍历,标记所有可以访问到的对象为“活跃的”。
    • 根对象包括全局对象、当前执行栈上的局部变量、活动函数等。
    • 遍历所有可达的对象,并将这些对象标记为“活动”状态,意味着它们仍然被程序所引用。
  2. 清除阶段(Sweep Phase)

    • 在标记阶段完成后,垃圾回收器会检查堆中的所有对象。
    • 所有没有被标记为活动的对象(即不再被任何其他对象引用的对象)将被认为是垃圾,可以回收并释放内存。
    • 这时,垃圾回收器会删除这些不再需要的对象,释放它们占用的内存空间。

标记-清除算法的详细步骤

  1. 根对象标记:从程序的根对象(如全局对象、函数参数等)开始,递归或迭代地标记所有可以访问到的对象。
  2. 对象遍历:遍历所有对象,并检查每个对象的引用(即指向其他对象的引用)。如果对象是“活动的”,则标记它为“存活”。
  3. 清理垃圾对象:一旦完成标记阶段,垃圾回收器会遍历堆内存,找出所有未被标记的对象,并将其标记为“垃圾”。这些垃圾对象会被销毁,释放内存。

示例

下面是一个包含多个对象的 JavaScript 程序:

function createObjects() {let obj1 = { name: 'Object 1' };let obj2 = { name: 'Object 2' };let obj3 = { name: 'Object 3' };obj1.ref = obj2;  // obj1 引用 obj2obj2.ref = obj3;  // obj2 引用 obj3// 假设程序中不再使用 obj1 和 obj3obj1 = null;  // 断开 obj1 和 obj2 的引用obj2 = null;  // 断开 obj2 和 obj3 的引用
}createObjects();

在上述代码中,obj1obj2obj3 都是对象,它们之间通过引用相互连接。在 createObjects 函数执行完后,obj1obj2 都被设置为 null,它们之间的引用被断开。

  1. 标记阶段:垃圾回收器从根对象(例如,函数调用栈、全局对象)开始,遍历所有活动对象。如果 obj1obj2 是活动的,它们会被标记为“活跃”对象。

  2. 清除阶段:由于 obj1obj2 已经被断开引用,它们不再可达,因此垃圾回收器会认为这些对象是垃圾并释放它们占用的内存。

3. 分代回收(Generational Collection)

分代回收基于一个观察:大多数对象的生命周期很短,只有少数对象会存活较长时间。因此,垃圾回收器将内存分为不同的代(Generation),并对不同代采用不同的回收策略。

  • 新生代(Young Generation):存放新创建的对象。新生代的垃圾回收频率较高,采用复制算法(Copying Algorithm)进行回收。

  • 老生代(Old Generation):存放存活时间较长的对象。老生代的垃圾回收频率较低,采用标记-清除或标记-整理(Mark-and-Compact)算法进行回收。

生成垃圾回收算法基于这样一个假设:大部分对象会很快变得不可达,因此,年轻代的对象会频繁进行垃圾回收。只有生命周期较长的对象才会进入老年代,老年代的回收相对较少,避免频繁回收带来的性能损耗。

二、如何避免内存泄漏

虽然垃圾回收器能够自动回收不再使用的对象,但开发者仍然需要注意以下几点,以避免内存泄漏:

1. 避免全局变量

全局变量在 JavaScript 中会一直存在,直到页面关闭。如果全局变量指向了不再需要的对象,这些对象就无法被垃圾回收器回收,造成内存泄漏。

2. 正确清理事件监听器

事件监听器如果没有移除,尤其是绑定到 DOM 元素上的事件监听器,会导致这些 DOM 元素无法被垃圾回收器回收,从而引发内存泄漏。

例如,使用 addEventListener 添加事件监听器时,记得使用 removeEventListener 移除它们。

const button = document.querySelector('button');
button.addEventListener('click', () => {console.log('Button clicked');
});// 当按钮不再需要时,移除事件监听器
button.removeEventListener('click', () => {console.log('Button clicked');
});

3. 使用 WeakMapWeakSet

WeakMapWeakSet 是一种内存友好的数据结构,它们的键或值是“弱引用”的,也就是说,当没有其他引用指向这些对象时,它们会被垃圾回收。它们非常适合用来存储一些不需要保持引用的对象。

4. 定时器和回调函数

定时器(如 setIntervalsetTimeout)如果没有被清除,可能导致相关的回调函数无法释放,从而引发内存泄漏。使用时一定要确保定时器在适当时机被清除。

const timer = setInterval(() => {console.log('Interval running');
}, 1000);// 在不需要时清除定时器
clearInterval(timer);

5. DOM 元素的引用

如果 DOM 元素被 JavaScript 引用且没有正确释放,即使该元素从页面中移除,JavaScript 引用也会阻止其被回收。确保在不需要使用 DOM 元素时及时解除引用。

let element = document.querySelector('.element');
// 当不再需要时,解除对该元素的引用
element = null;

结论

浏览器的垃圾回收机制是JavaScript内存管理的核心,理解其工作原理和优化策略对于构建高性能的Web应用至关重要。通过减少全局变量、及时解除引用、避免循环引用、使用对象池和优化数据结构,可以有效地减少垃圾回收的开销,提升应用的性能和用户体验。

参考文献:

  • MDN Web Docs: Memory Management

  • V8 JavaScript Engine: Garbage Collection

  • JavaScript.info: Garbage Collection

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Easysearch 可搜索快照功能,看这篇就够了

可搜索快照功能改变了我们对备份数据的查询方式。以往要查询备份数据时,要先找到备份数据所在的快照,然后在一个合适的环境中恢复快照,最后再发起请求查询数据。这个处理路径很长,而且很消耗时间。可搜索快照功能将大大简化该处理路径,节约时间。 角色设置 相信你对节点角…

基本共射极放大电路的分析

静态分析利用直流通路求Q点(静态工作点)\[I_{BQ}=\frac{V_{BB}-V_{BEQ}}{R_{b}} \]一般硅管\(V_{BE}=0.7V\),锗管\(V_{BE}=0.2V\),\(\beta\)已知 \[I_{CQ}=\beta I_{BQ} \]\[V_{CEQ}=(\frac{V_{CC}-I_{CQ}}{R_{c}}-I_{CQ})R_L \]动态分析交流通路分析画小信号等效模型\[r_{…

使用扣子实现营销获客套电机器人-工作流+多维表格+飞书机器人

V+: llike620 就是利用扣子的工作流,实现简单的获取线索机器人,然后对接在抖音音私信上 主要用于某汽车贴膜产品的获客,先获取车型,再获取联系方式增加了状态机制,不能让对方跳过业务流程新线索存入飞书多维表格,并通过飞书机器人进行通知 十年开发经验程序员,离职全心…

Qt配置和功能使用说明

1、执行qmake qmake 是 Qt 提供的一个构建工具,用于简化跨平台 Qt 应用的构建流程。它通过读取 .pro 文件生成适合目标平台的构建文件,例如 Makefile(windows & linux) 或 Visual Studio 工程文件(windows)2、Shadow Build(影子构建) Shadow Build 是 Qt Creator 提…

静态工作点对波形失真的影响

一、截止失真原因:当静态工作点设置过低,即\(I_{BQ}\)过小,\(V_{CEQ}\)过大时,输入信号的负半周可能会使晶体管进入截止区。 现象:输出波形的正半周被削顶,即正半周顶部被“切掉”一部分,这是因为在截止区,晶体管的集电极电流\(i_C\)几乎为零,不能跟随输入信号的变化而…

基本共射极放大电路

以下是关于基本共射极放大电路的详细介绍: 电路结构由晶体管(通常是BJT)、直流电源\(V_{CC}\)、基极偏置电阻\(R_b\)、集电极负载电阻\(R_c\)、输入电容\(C_1\)、输出电容\(C_2\)以及输入信号源\(v_s\)和负载电阻\(R_L\)组成。工作原理直流偏置:通过\(R_b\)和\(V_{CC}\)为晶…

20241322 《计算机基础与程序设计》课程总结

2024-2025-1 20241322 《计算机基础与程序设计》第十五周学习总结 作业信息 |这个作业属于哪个课程|https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP| |这个作业要求在哪里|https://www.cnblogs.com/rocedu/p/9577842.html#WEEK15| |这个作业的目标|课程总结,文中的链…

《docker基础篇:7.Docker容器数据卷》包括坑、回顾下上一讲的知识点,参数V、是什么、更干嘛、数据卷案例

《docker基础篇:7.Docker容器数据卷》包括坑、回顾下上一讲的知识点,参数V、是什么、更干嘛、数据卷案例@目录7.Docker容器数据卷7.1坑7.2 回顾下上一讲的知识点,参数V7.3 是什么7.4 能干嘛7.5 数据卷案例7.5.1 宿主vs容器之间映射添加容器卷7.5.2 读写规则映射添加说明7.5.…

DIY笔记本散热器

前言我用的笔记本是R9000P 2021H,用了快三年才发现笔记本发热量有点高,GPU 3070倒是还好不用担心过热的问题,主要是这个CPU 5800H非常积热。最近也是清完灰、涂硅脂、换完风扇了,双烤测试了下功耗能到200W但是CPU有大概70C往上的样子,考虑到这是冬季测试下的结果,这个成绩…

BJT的共射极伏安特性曲线

BJT(双极型晶体管)共射极的伏安特性曲线包括输入特性曲线和输出特性曲线,以下是详细讲解:输入特性曲线定义:描述基极电流\(i_B\)与基极-发射极电压\(v_{BE}\)之间的关系,通常以集电极-发射极电压\(v_{CE}\)为参变量,即\(i_B = f(v_{BE})|_{v_{CE}=constant}\)。 曲线形状…

检索增强生成和思维链结合: 如何创建检索增强思维链 (RAT)?

论文地址:https://arxiv.org/pdf/2403.05313 Github地址:https://github.com/CraftJarvis/RAT 想象一下,一个人工智能助手可以像莎士比亚一样写作,像专家一样推理。这听起来很了不起,对吧?但是,如果这个助手有时难以确保事实准确性,依赖过时的信息或只是编造事实,该怎…