一篇文章弄懂javascript内存泄漏

1、什么是内存泄漏

在了解什么是内存泄漏之前, 我们应该要对内存是什么有个概念, 随机存取存储器(英语:Random Access Memory,缩写:RAM)是与 CPU 直接交换数据的内部存储器。它可以随时读写, 而且速度很快,通常作为操作系统或其他正在运行中的程序的临时资料存储介质。

什么是内存泄漏? :

程序不再需要使用的内存, 但是又没有及时释放, 就叫做内存泄漏!

然后在理解泄漏之前, 我们的了解下内存的管理, 在一些底层语言中, 如C语言, 内存是需要开发者自己分配和释放的, 通过malloc、free等函数进行内存管理.

<pre class="custom">`char * buffer;
// 使用malloc申请内存空间
buffer = (char*) malloc (66);
// do something ...
// 不需要时, 释放掉内存空间引用
free(buffer);

上面这一小段代码就是C语言中关于内存的申请和释放, 但是众所周知在javascript中是不需要开发者手动管理内存的, 在Chrome中有V8引擎帮我们自动进行内存的分配和回收, 这就是垃圾回收机制, 但这并不代表我们在编写代码是不需要考虑内存的事情, 因为V8垃圾回收机制是有特定的规则的, 了解这些规则可以让我们避免写出内存泄漏的烂代码.

那么先了解下javascript垃圾回收机制常见的两种方法吧:

  • 引用计数算法
  • 标记清除算法

引用计数法

IE使用的是引用计数算法, 这种方法无法解决循环引用的垃圾回收问题, 容易造成内存泄漏

那么什么是引用计数算法呢? 什么又是循环引用问题呢?

所谓引用计数即, 我们有一个变量每次被引用GC机制就会给这个变量计数加一, 当引用减少就计数减一, 如果计数为零, 在下一次垃圾回收时, 就会被释放掉.

<pre class="custom">`let obj = {}; // obj计数为0
let a = obj; // obj计数为1
let b = obj; // obj计数为2
let a = null; // obj计数为1
let b = null; // Obj计数为0, 下次垃圾回收将obj内存释放

以上代码演示的就是引用计数法垃圾回收机制, 当存在循环引用的情况就没救了

<pre class="custom">`let obj = {};
let obj2 = {};
obj.o = obj2; // obj2计数为1
obj2.o = obj; // obj计数为1

这就是循环引用, 所以垃圾回收机制并不会对obj, obj2进行内存释放, 变量常驻内存, 导致内存泄漏.

那么说完了引用计数法, 我们再来看看主流浏览器目前所用的垃圾回收算法 – 标记清除法,

从2012年起,所有现代浏览器都使用了标记清除垃圾回收算法。所有对JavaScript垃圾回收算法的改进都是基于标记-清除算法的改进,并没有改进标记-清除算法本身和它对“对象是否不再需要”的简化定义。

标记清除法

这个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后继续找这些对象引用的对象.

在开始说标记清除法之前, 补说一个知识点, 就是栈和堆的概念, 看看下面的例子

<pre class="custom">`let obj = {};
let obj2 = {};

obj = null;
obj2 = null;

我们知道, 在javascript中, 除了八大基本类型(截至目前为止是八种), 剩下的都是对象类型, 在js中对象类型都是引用类型, 内容的实体是存在堆中的, 如下面我画的这张图所示:

当我们重新赋值obj, obj1的时候内存结构会变成这样

堆内存中的对象没有人引用他们, 但是他们还占用这内存, 这时候就需要我们的垃圾回收出场销毁他们了, V8引擎的垃圾回收机制不仅销毁掉堆内存中无人引用的空间, 还会对堆内存进行碎片整理, V8的GC(垃圾回收)工作如下面动图所示:

V8的GC大致可以分为以下几个步骤

第一步,通过 GC Root 标记空间中活动对象和非活动对象。目前V8采用的是可访问性算法, 从GC Root出发遍历所有的对象, 通过GC Root可以遍历到的标记为可访问的, 称为活动对象,必须保留在内存中, GC Root无法遍历到的标记为不可访问的, 称为非活动对象, 这些不可访问的对象将会被GC清理掉.

第二步,回收非活动对象所占据的内存。其实就是在所有的标记完成之后,统一清理内存中所有被标记为可回收的对象。

第三步,做内存整理。一般来说,频繁回收对象后,内存中就会存在大量不连续空间,我们把这些不连续的内存空间称为内存碎片。

受代际假说的影响, V8引擎采用两个垃圾回收器, 主垃圾回收器–Major GC、副垃圾回收器–Minor GC(Scavenger), 你可能会问什么是代际假说:

第一个是大部分对象都是“朝生夕死”的,也就是说大部分对象在内存中存活的时间很短,比如函数内部声明的变量,或者块级作用域中的变量,当函数或者代码块执行结束时,作用域中定义的变量就会被销毁。因此这一类对象一经分配内存,很快就变得不可访问;

第二个是不死的对象,会活得更久,比如全局的 window、DOM、Web API 等对象。

这两个回收器的作用如下:

  • 主垃圾回收器 -Major GC,主要负责老生代的垃圾回收。
  • 副垃圾回收器 -Minor GC (Scavenger),主要负责新生代的垃圾回收。

这里又会引出新生代内存和老生代内存的概念, 将堆内存分成两块区域

新生代的内存区域一般比较小, 但是垃圾回收得会比较频繁, 而老生代内存区的特点就是对象占用空间相对较大, 对象存活时间较长, 垃圾回收的频率也较低.

对了补一句, 垃圾回收时是会阻塞进程的.

2、常见的内存泄漏情况

了解垃圾回收和内存泄漏是什么之后, 我们来看一些常见的内存泄漏场景:

1. 意外的全局变量

前面我们提到有些对象是常驻内存的, 视为不死对象, 如window对象, 是浏览器中javascript的顶级对象, 它的存在贯穿这个javascript的生命周期, 如果我们不小心把庞大又用不上的变量挂到了window对象上, 将会造成内存泄漏, 当然这是一个很低级的错误.

<pre class="custom">`function test() {
  // 漏掉了声明, 将会自动挂载到window对象下
  str = '';
  for (let i = 0; i < 100000; i++) {
   str += 'xx';
  }
  return str;
}
// test执行结束后, str应该就没用了, 但是它常驻在了内存中
test();

2. 滥用闭包

此处来顺便了解下闭包的概念, 闭包的概念网上很多说的都比较抽象, 我个人理解的闭包是:

函数和其可操作的其他作用域变量的词法环境称为闭包

当然了如果我是个杠精, 可能会说with语法是不是也算闭包呢? 按照定义with不是函数所以不属于闭包.

MDN中对闭包的描述:

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

闭包是静态作用域(又称为词法作用域)语言独有的功能.

<pre class="custom">`function fn() {
  const x = 'xx';
  return function() {
    return x;
 }
}
const getX = fn();
console.log(getX());

如上面的例子就是一个闭包, fn执行结束之后内部变量并没有销毁, 我们在全局作用域下可以通过getX访问到fn函数作用域内的变量x.

如果不好理解可以看下上面提到的静态作用域(又称词法作用域), 看到静态作用域应该你会问, 有没有动态作用域呢, 但是是有的, bash脚本采用的就是动态作用域, javascript采用的是静态作用域, 闭包是静态作用域采用的功能.

看两个例子

<pre class="custom">`const x = 123;
function fn() {
  console.log(x);
}
function fn2() {
  const x = 345;
  fn();
}
fn2(); // 结果是123

静态作用域: fn中输出的x所处的作用域是在定义时确定的

再看个动态作用域的例子

<pre class="custom">`# test.sh
value="global";
function fn() {
 echo $value;
}
function fn2() {
 local value="local";
 fn;
}
fn2; # 结果是local

动态作用域: fn中输出的x所处的作用域是在调用时确定的

还有一点, 只有滥用闭包才能叫内存泄漏, 因为根据定义只有我们用不到了, 而且没有被销毁的内存才叫内存泄漏, 闭包中的值是我们用到的所以不应该叫做内存泄漏.

<pre class="custom">`function generateRandomMath() {
 let x = Math.random();
  return function() {
    return x;
  }
}

如上面这个例子就是滥用闭包了, 就该叫做内存泄漏

3. 被遗忘的定时器

这个没什么好说的, 就是设置了定时器请记住在不要的时候使用clearInterval或者clearTImeout给他关一下.

4. DOM相关

给某个dom节点绑定了很多事件, 使用过程中dom节点被移除但是被释放内存

我们来看个例子

<pre class="custom">`<button class="remove">remove bbb</button>
<div class="box">bbb</div>
<script> const box = document.querySelector('.box');

  document.querySelector('.remove').addEventListener('click', () => {
    document.body.removeChild(box);
    console.log(box);
  }) </script>

移除了box元素后, box仍然占用内存, 这也是内存泄漏, 因为box用不到了但是没有释放内存

总结

到此这篇关于javascript内存泄漏的文章就介绍到这了,更多相关javascript内存泄漏内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详谈JavaScript内存泄漏

    1.什么是闭包.以及闭包所涉及的作用域链这里就不说了. 2.JavaScript垃圾回收机制 JavaScript不需要手动地释放内存,它使用一种自动垃圾回收机制(garbage collection).当一个对象无用的时候,即程序中无变量引用这个对象时,就会从内存中释放掉这个变量. 复制代码 代码如下: var s = [ 1, 2 ,3];     var s = null;     //这样原始的数组[1 ,2 ,3]就会被释放掉了. 3.循环引用 三个对象 A .B .C AàBàC :

  • 防止动态加载JavaScript引起的内存泄漏问题

    为了释放脚本资源,通常在返回后还要一些进行额外的处理. 复制代码 代码如下: script = document.createElement('script'); script.src = 'http://example.com/cgi-bin/jsonp?q=What+is+the+meaning+of+life%3F'; script.id = 'JSONP'; script.type = 'text/javascript'; script.charset = 'utf-8'; // 标签加

  • JS常见内存泄漏及解决方案解析

    内存泄漏? 官方解释:内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果. 通俗点就是指由于疏忽或者错误造成程序未能释放已经不再使用的内存,不再用到的内存却没有及时释放,从而造成内存上的浪费. 避免内存泄漏? 在局部作用域中,等函数执行完毕,变量就没有存在的必要了,垃圾回收机制很亏地做出判断并且回收,但是对于全局变量,很难判断什么时候不用这些变量,无法正常回收:所以,尽量少使用全局变量.在

  • javascript垃圾收集机制与内存泄漏详细解析

    javascript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中的使用的内存.而在C和C++之类的语言中,开发人员的一项基本任务就是手动跟踪内存的使用情况,这是造成许多问题的一个根源.在编写javascript程序时候,开发人员不用再关心内存使用的问题,所需内存的分配 以及无用的回收完全实现了自动管理.这种垃圾收集机制的原理其实很简单:找出那些不再继续使用的变量,然后释放其中占用的内存.为此,垃圾收集器会按照固定的时间间隔(或代码执行中预设的收集时间),周期性的执行这一操作.

  • 深入理解JavaScript程序中内存泄漏

    垃圾回收解放了我们,它让我们可将精力集中在应用程序逻辑(而不是内存管理)上.但是,垃圾收集并不神奇.了解它的工作原理,以及如何使它保留本应在很久以前释放的内存,就可以实现更快更可靠的应用程序.在本文中,学习一种定位 JavaScript 应用程序中内存泄漏的系统方法.几种常见的泄漏模式,以及解决这些泄漏的适当方法. 一.简介 当处理 JavaScript 这样的脚本语言时,很容易忘记每个对象.类.字符串.数字和方法都需要分配和保留内存.语言和运行时的垃圾回收器隐藏了内存分配和释放的具体细节. 许

  • 插件:检测javascript的内存泄漏

    转自:http://www.ajaxjs.com/yuicn/bbs/ShowPost.asp?ThreadID=6 2006-10-18 @ 07:59:29 · 作者 volcano Javascript的内存泄漏,不是太可怕.它只会悄悄的,慢慢的把你的浏览器拖的巨慢无比,让你愤怒的拍案而起,大骂微软出品的破烂浏览器危害社会.这一切有可能并不是浏览器的错,可能只是因为网页上有些javascript的内存泄漏罢了. 在科技日益发达今天,我们有必要武装自己,以及自己的浏览器,这样万一浏览器倒下了

  • JavaScript中内存泄漏的介绍与教程(推荐)

    本文主要给大家详细介绍了关于JavaScript中内存泄漏的相关内容,文中介绍的非常详细,对大家具有一定的参考学习价值,下面来一起看看详细的介绍: 一.什么是内存泄漏? 程序的运行需要内存.只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存. 对于持续运行的服务进程(daemon),必须及时释放不再用到的内存.否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃. 不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak). 有些语言(比如 C 语言)必须手动

  • JS造成内存泄漏的几种情况实例分析

    本文实例讲述了JS造成内存泄漏的几种情况.分享给大家供大家参考,具体如下: 介绍: js中的内存垃圾回收机制:垃圾回收器会定期扫描内存,当某个内存中的值被引用为零时就会将其回收.当前变量已经使用完毕但依然被引用,导致垃圾回收器无法回收这就造成了内存泄漏.传统页面每次跳转都会释放内存,所以并不是特别明显. Vue单页面应用中:Web App 与 传统Web的区别,因为Web App是单页面应用页面通过路由跳转不会刷新页面,导致内存泄漏不断堆积,导致页面卡顿. 泄漏点: 1.DOM/BOM 对象泄漏

  • JavaScript中的垃圾回收与内存泄漏示例详解

    前言 程序的运行需要内存.只要程序提出要求,操作系统或者运行时就必须供给内存.所谓的内存泄漏简单来说是不再用到的内存,没有及时释放.为了更好避免内存泄漏,我们先介绍Javascript垃圾回收机制. 在C与C++等语言中,开发人员可以直接控制内存的申请和回收.但是在Java.C#.JavaScript语言中,变量的内存空间的申请和释放都由程序自己处理,开发人员不需要关心.也就是说Javascript具有自动垃圾回收机制(Garbage Collecation). 一.垃圾回收的必要性 下面这段话

  • JavaScript内存泄漏的处理方式

    下面就是小编整理的关于JS遇到内存泄漏问题时应该采取的处理方式. 随着现在的编程语言功能越来越成熟.复杂,内存管理也容易被大家忽略.本文将会讨论JavaScript中的内存泄漏以及如何处理,方便大家在使用JavaScript编码时,更好的应对内存泄漏带来的问题. 概述 像C语言这样的编程语言,具有简单的内存管理功能函数,例如malloc( )和free( ).开发人员可以使用这些功能函数来显式地分配和释放系统的内存. 当创建对象和字符串等时,JavaScript就会分配内存,并在不再使用时自动释

随机推荐