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

类型:

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

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

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

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

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

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

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

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

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

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

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

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

内存的生命周期:

JS 的内存分配

为了不让程序员费心分配内存,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 的内存使用

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

var a = 10; // 分配内存
console.log(a); // 对内存的使用

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

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

导致内存泄漏的情况:

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

例子:

1.意外创建全局变量

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

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

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

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


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

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

3.闭包

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

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


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 中

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 元素的引用。

            

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值