浏览器的垃圾回收机制与内存泄漏

类型:

浏览器的垃圾回收机制通常分为两种类型:标记清除和引用计数。

  1. 标记清除:这是一种常见的垃圾回收算法,它通过标记不再使用的对象,然后清除这些对象来释放内存空间。当一个对象不再被引用时,垃圾回收器会标记这个对象,并在适当的时候清除它。

  2. 引用计数:这种垃圾回收算法会对每个对象进行引用计数,当一个对象的引用计数为0时,说明这个对象不再被使用,垃圾回收器会立即清除这个对象。这种算法简单高效,但是无法处理循环引用的情况。

除了这两种基本的垃圾回收算法,现代浏览器还会结合其他技术来提高垃圾回收的效率,比如增量标记、并行标记等。这些技术可以减少垃圾回收的停顿时间,提高用户体验。

理解垃圾回收的重要概念:

  1. 内存管理:浏览器使用内存管理器来分配和释放内存。当我们创建对象或变量时,内存管理器会分配一块内存空间给它们,当这些对象或变量不再被引用时,内存管理器会释放这些内存空间。

  2. 垃圾回收器:浏览器中的垃圾回收器负责检测不再使用的对象,并释放它们占用的内存空间。垃圾回收器会定期扫描内存,标记不再使用的对象,然后清除这些对象。

  3. 内存泄漏:内存泄漏是指程序中的对象在不再需要时仍然占用内存空间,导致内存资源浪费。常见的内存泄漏原因包括未及时释放对象、循环引用等。

  4. 垃圾回收算法:浏览器的垃圾回收器使用不同的算法来检测和清除不再使用的对象。除了标记清除和引用计数算法外,还有增量标记、并行标记、分代回收等算法来提高垃圾回收的效率。

  5. 垃圾回收的影响:垃圾回收会造成一定的性能开销,因为在清除不再使用的对象时,浏览器可能会暂停执行JavaScript代码。因此,我们应该尽量避免频繁创建和销毁对象,以减少垃圾回收的频率。

JavaScript,会在创建变量时自动分配内存,并且在不再使用它们时"自动"释放内存,因为自动垃圾回收机制的存在,让大多Javascript开发者感觉他们可以不关心内存管理,所以会在一些情况下导致内存泄漏。

浏览器如何进行内存管理呢?

内存的生命周期:

JS 的内存分配

为了不让程序员费心分配内存,JavaScript 在定义变量时就完成了内存分配。

javascript 复制代码
// 给数值变量分配内存
var n = 123;

// 给字符串分配内存
var s = "azerty"; 

// 给对象及其包含的值分配内存
var o = {
 a: 1,
 b: null
}; 

// 给数组及其包含的值分配内存(就像对象一样)
var a = [1, null, "abra"]; 

// 给函数(可调用的对象)分配内存
function f(a){
 return a + 2;
} 

// 函数表达式也能分配一个对象
someElement.addEventListener('click', function(){
 someElement.style.backgroundColor = 'blue';
}, false);

// 给函数调用结果分配内存对象
var d = new Date(); // 分配一个 Date 对象
var e = document.createElement('div'); // 分配一个 DOM 元素

// 有一些方法能返回一些新内容
var arr = [1,2,3].map(item=> item>1)
var arr = [1,2,3].concat([4,5,6])

JS 的内存使用

使用值的过程实际上是对分配内存进行读取与写入的操作。 读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数。

javascript 复制代码
var a = 10; // 分配内存
console.log(a); // 对内存的使用

上面提到一个概念,内存泄漏

JS里已经分配内存地址的对象,但是由于长时间没有释放或没办法清除,造成长期占用内存的现象,最终导致运行速度慢,甚至崩溃的现象

导致内存泄漏的情况:

  1. 一些未声明直接赋值的变量;
  2. 一些未清空的定时器;
  3. 过渡的闭包;
  4. 一些引用元素没有被清空

例子:

1.意外创建全局变量

javascript 复制代码
function foo(){
    a = 'some text' // 没有声明变量 实际上是全局变量 => window.a
    this.b = 'some text' // 全局变量 => window.b
}

意外地创建了两个全局变量,在调用完foo 之后,a和b依然会存在window上

2.遗忘的定时器和回调函数

在很多库中, 如果使用了观察者模式, 都会提供回调方法, 来调用一些回调函数。 要记得回收这些回调函数。举一个 setInterval的例子:

javascript 复制代码
var serverData = loadData();
setInterval(function() {
 var box= document.getElementById('box');
 if(box) {
     box.innerHTML = JSON.stringify(serverData);
 }
}, 5000); // 每 5 秒调用一次

如果后续 renderer 元素被移除,整个定时器实际上没有任何作用。 但如果你没有回收定时器,整个定时器依然有效, 不但定时器无法被内存回收, 定时器函数中的依赖也无法回收。在这个案例中的 serverData 也无法被回收。

3.闭包

当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包。

下面这种情况下,闭包也会造成内存泄露:

javascript 复制代码
var theThing = null;
var replaceThing = function () {
    var originalThing = theThing;
    var unUsed = function () {
    if (originalThing) // 对于 'originalThing'的引用
         console.log("hi");
    };
    theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
        console.log("message");
    }
   };
};
setInterval(replaceThing, 1000);

,闭包之间是共享作用域的,由于unUsed引用了theThing,虽然一直没有被调用,但是someMethod可能会在其它地方调用,就导致无法对其内存进行回收

4.dom引用

很多时候, 我们对 Dom 的操作, 会把 Dom 的引用保存在一个数组或者 Map 中

javascript 复制代码
var elements = {
 image: document.getElementById('image')
};
function doStuff() {
 elements.image.src = 'http://example.com/image_name.png';
}
function removeImage() {
 document.body.removeChild(document.getElementById('image'));
 // 这个时候我们对于 #image 仍然有一个引用, Image 元素, 仍然无法被内存回收.
}

举个例子: 如果我们引用了一个表格中的td元素,一旦在 Dom 中删除了整个表格,我们直观的觉得内存回收应该回收除了被引用的 td 外的其他元素。

但是事实上,这个 td 元素是整个表格的一个子元素,并保留对于其父元素的引用。

这就会导致对于整个表格,都无法进行内存回收。所以要小心处理对于 Dom 元素的引用。

相关推荐
bin91531 小时前
DeepSeek 助力 Vue 开发:打造丝滑的复制到剪贴板(Copy to Clipboard)
前端·javascript·vue.js·ecmascript·deepseek
软件黑马王子1 小时前
C#初级教程(4)——流程控制:从基础到实践
开发语言·c#
闲猫1 小时前
go orm GORM
开发语言·后端·golang
晴空万里藏片云2 小时前
elment Table多级表头固定列后,合计行错位显示问题解决
前端·javascript·vue.js
奶球不是球2 小时前
el-button按钮的loading状态设置
前端·javascript
李白同学3 小时前
【C语言】结构体内存对齐问题
c语言·开发语言
无责任此方_修行中4 小时前
每周见闻分享:杂谈AI取代程序员
javascript·资讯
黑子哥呢?4 小时前
安装Bash completion解决tab不能补全问题
开发语言·bash
青龙小码农4 小时前
yum报错:bash: /usr/bin/yum: /usr/bin/python: 坏的解释器:没有那个文件或目录
开发语言·python·bash·liunx
大数据追光猿4 小时前
Python应用算法之贪心算法理解和实践
大数据·开发语言·人工智能·python·深度学习·算法·贪心算法