JS的垃圾回收机制与常见内存泄露的解决方法

2022-05-29  本文已影响0人  Michael113c

因为项目需要涉及到了垃圾回收机制和内存泄漏,查看网上了许多教程,未免以后遗忘故此记录下来以便以后查阅。

在不需要字符串、对象的时候,需要释放其所占用的内存,否则将会消耗完系统中所有可用的内存,造成系统崩溃,这就是垃圾回收机制所存在的意义

所谓的内存泄漏指的是:由于疏忽或错误造成程序未能释放那些已经不再使用的内存,造成内存的浪费。

垃圾回收

javascript不同于c、c++的一个特点是:具有自动的垃圾回收机制,内存的分配以及内存的回收完全实现了自动管理,减少了许多不必要的麻烦。

所谓的垃圾回收就是找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间),周期性地执行这一操作。
全局变量的生命周期直至浏览器卸载页面才会结束,也就是说全局变量不会被当成垃圾回收

实现垃圾回收的方式主要有两种:标记清除,.引用计数

1.标记清除

目前最常用的垃圾回收的方式,也是当前浏览器所采用的垃圾收集策略。

原理

在标记清除的方式中有两个概念:『进入环境』和『离开环境』。『进入环境』指变量进入执行的环境。『离开环境』指变量完成任务,离开了执行的环境。

当变量进入环境时(例如在函数中声明一个变量),将这个变量标记为“进入环境”,当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。

流程

  1. 垃圾收集器在运行的时候给存储在内存中的所有变量都加上标记
  2. 去掉环境中的变量以及被环境中的变量引用的变量的标记
  3. 那些还存在标记的变量则被视为准备删除的变量。
  4. 最后垃圾收集器会执行内存清除的工作,销毁那些带标记的值并回收它们所占用的内存空间

2.引用计数

原理:
引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加 1。
相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这 个值了,因而就可以将其占用的内存空间回收回来。
这样,当垃圾收集器下次再运行时,它就会释放那 些引用次数为零的值所占用的内存。

不过这里潜在的一个问题是:循环引用时,两个对象都至少被引用了一次,将不能自动被回收。所以导致,我们常讲的内存泄露。所以这种机制在js中并不常用。

『循环引用』指的是对象 A 中包含一个指向对象 B 的指针,而对象 B 中也包含一个指向对象 A 的引用。对于像js类的自动回收机制的语言来说,需要额外手动的去释放内存,其实并不友好,例如,在下面的例子中:

function ftc(){
    var A = new Object();
    var B = new Object();
    A.property = B; 
    B.property = A;
}

内存泄漏的原因

虽然js有垃圾回收机制,但我们在编写代码的时候,有些情况还是会造成内存泄漏,了解这些情况,并在编写程序的时候,注意避免,我们的程序会更加完善。

1.上文我们提到了全局变量不会被当成垃圾回收,我们在编码中有时会出现下面这种情况:

 function foo() {
     this.bar2 = '默认绑定this指向全局' // 全局变量=> window.bar2
      bar = '全局变量'; // 没有声明变量 实际上是全局变量=>window.bar
   }
 foo();

当我们使用默认绑定,this会指向全局,this.something也会创建一个全局变量,这一点可能很多人没有注意到。

解决方法:在函数内使用严格模式or细心一点

function foo() {
      "use strict"; 
      this.bar2 = "严格模式下this指向undefined"; 
      bar = "报错";
    }
    foo();

2.当不需要setInterval或者setTimeout时,定时器没有被clear,定时器的回调函数以及内部依赖的变量都不能被回收,造成内存泄漏。

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        node.innerHTML = JSON.stringify(someResource));
        // 定时器也没有清除
    }
    // node、someResource 存储了大量数据 无法回收
}, 1000);

解决方法: 在定时器完成工作的时候,手动清除定时器。

3.循环引用问题,上文有提到

4.没有清理DOM元素引用:
html:

<div id="test">
    <span>1</span>
    <span>2</span>
  </div>

js:

var refA = document.getElementById("test");
document.body.removeChild(refA); // dom删除了
console.log(refA, "refA");  // 但是还存在引用 能console出整个div 没有被回收
refA = null;//解决方法
console.log(refA, "refA");  // 解除引用

5.console保存大量数据在内存中。过多的console,比如定时器的console会导致浏览器卡死。

如何避免内存泄漏

  1. 减少不必要的全局变量,使用严格模式避免意外创建全局变量。
  2. 在你使用完数据后,及时解除引用(闭包中的变量,dom引用,定时器清除)。
  3. 组织好你的逻辑,避免死循环等造成浏览器卡顿,崩溃的问题。
上一篇下一篇

猜你喜欢

热点阅读