什么是内存泄漏

内存泄漏基本上就是不再被应用需要的内存,由于某种原因,没有被归还给操作系统或者进入可用内存池。

编程语言喜欢不同的管理内存方式。然而,一段确定的内存是否被使用是一个不可判断的问题。换句话说,只有开发者才能弄清楚,是否一段内存可以被还给操作系统。

某些编程语言为开发者提供了释放内存功能。另一些则期待开发者清楚的知道一段内存什么时候是没用的。Wikipedia有一篇非常好的关于内存管理的文章。

4种常见的JavaScript内存泄漏

1:全局变量

JavaScript用一个有趣的方式管理未被声明的变量:对未声明的变量的引用在全局对象里创建一个新的变量。在浏览器的情况下,这个全局对象是 window 。换句话说:

  1.  
    function foo(arg) {
  2.  
    bar = "some text";
  3.  
    }

等同于

  1.  
    function foo(arg) {
  2.  
    window.bar = "some text";
  3.  
    }

如果 bar 被假定只在 foo 函数的作用域里引用变量,但是你忘记了使用 var 去声明它,一个意外的全局变量就被声明了。

在这个例子里,泄漏一个简单的字符串不会造成很大的伤害,但是它确实有可能变得更糟。

另外一个意外创建全局变量的方法是通过 this :

  1.  
    function foo() {
  2.  
    this.var1 = "potential accidental global";
  3.  
    }
  4.  
     
  5.  
    // Foo作为函数调用,this指向全局变量(window)
  6.  
    // 而不是undefined
  7.  
    foo();

为了防止这些问题发生,可以在你的JaveScript文件开头使用 'use strict'; 。这个可以使用一种严格的模式解析JavaScript来阻止意外的全局变量。

除了意外创建的全局变量,明确创建的全局变量同样也很多。这些当然属于不能被回收的(除非被指定为null或者重新分配)。特别那些用于暂时存储数据的全局变量,是非常重要的。如果你必须要使用全局变量来存储大量数据,确保在是使用完成之后为其赋值 null或者重新赋其他值。

2: 被遗忘的定时器或者回调

在JavaScript中使用 setInterval 是十分常见的。

大多数库,特别是提供观察器或其他接收回调的实用函数的,都会在自己的实例无法访问前把这些回调也设置为无法访问。但涉及 setInterval 时,下面这样的代码十分常见:

  1.  
    var serverData = loadData();
  2.  
    setInterval(function() {
  3.  
    var renderer = document.getElementById('renderer');
  4.  
    if(renderer) {
  5.  
    renderer.innerHTML = JSON.stringify(serverData);
  6.  
    }
  7.  
    }, 5000); //每5秒执行一次

定时器可能会导致对不需要的节点或者数据的引用。

renderer 对象在将来有可能被移除,让interval处理器内部的整个块都变得没有用。但由于interval仍然起作用,处理程序并不能被回收(除非interval停止)。如果interval不能被回收,它的依赖也不可能被回收。这就意味着 serverData ,大概保存了大量的数据,也不可能被回收。

在观察者的情况下,在他们不再被需要(或相关对象需要设置成不能到达)的时候明确的调用移除是非常重要的。

在过去,这一点尤其重要,因为某些浏览器(旧的IE6)不能很好的管理循环引用(更多信息见下文)。如今,大部分的浏览器都能而且会在对象变得不可到达的时候回收观察处理器,即使监听器没有被明确的移除掉。然而,在对象被处理之前,要显式地删除这些观察者仍然是值得提倡的做法。例如:

  1.  
    var element = document.getElementById('launch-button');
  2.  
    var counter = 0;
  3.  
     
  4.  
    function onClick(event) {
  5.  
    counter++;
  6.  
    element.innerHtml = 'text ' + counter;
  7.  
    }
  8.  
     
  9.  
    element.addEventListener('click', onClick);
  10.  
     
  11.  
    // 做点事
  12.  
     
  13.  
    element.removeEventListener('click', onClick);
  14.  
    element.parentNode.removeChild(element);
  15.  
     
  16.  
    // 当元素被销毁
  17.  
    //元素和事件都会即使在老的浏览器里也会被回收

如今的浏览器(包括IE和Edge)使用现代的垃圾回收算法,可以立即发现并处理这些循环引用。换句话说,先调用 removeEventListener 再删节点并非严格必要。

jQuery等框架和插件会在丢弃节点前删除监听器。这都是它们内部处理,以保证不会产生内存泄漏,甚至是在有问题的浏览器(没错,IE6)上也不会。

3: 闭包

闭包是JavaScript开发的一个关键方面:一个内部函数使用了外部(封闭)函数的变量。由于JavaScript运行时实现的不同,它可能以下面的方式造成内存泄漏:

  1.  
    var theThing = null;
  2.  
     
  3.  
    var replaceThing = function () {
  4.  
     
  5.  
    var originalThing = theThing;
  6.  
    var unused = function () {
  7.  
    if (originalThing) // 引用'originalThing'
  8.  
    console.log("hi");
  9.  
    };
  10.  
     
  11.  
    theThing = {
  12.  
    longStr: new Array(1000000).join('*'),
  13.  
    someMethod: function () {
  14.  
    console.log("message");
  15.  
    }
  16.  
    };
  17.  
    };
  18.  
     
  19.  
    setInterval(replaceThing, 1000);

这段代码做了一件事:每次 ReplaceThing 被调用, theThing 获得一个包含大数组和新的闭包( someMethod )的对象。同时,变量 unused 保持了一个引用 originalThing ( theThing 是上次调用 replaceThing 生成的值)的闭包。已经有点困惑了吧?最重要的事情是 一旦为同一父域中的作用域产生闭包,则该作用域是共享的。

这里,作用域产生了闭包, someMethod 和 unused 共享这个闭包中的内存。 unused 引用了 originalThing 。尽管 unused 不会被使用, someMethod 可以通过 theThing 来使用 replaceThing 作用域外的变量(例如某些全局的)。而且 someMethod 和 unused 有共同的闭包作用域, unused 对 originalThing 的引用强制 oriiginalThing 保持激活状态(两个闭包共享整个作用域)。这阻止了它的回收。

当这段代码重复执行,可以观察到被使用的内存在持续增加。垃圾回收运行的时候也不会变小。从本质上来说,闭包的连接列表已经创建了(以 theThing 变量为根),这些闭包每个作用域都间接引用了大数组,导致大量的内存泄漏。

这个问题被Meteor团队发现,他们有描述了闭包大量的细节。

4: DOM外引用

有的时候在数据结构里存储DOM节点是非常有用的,比如你想要快速更新一个表格几行的内容。此时存储每一行的DOM节点的引用在一个字典或者数组里是有意义的。此时一个DOM节点有两个引用:一个在dom树中,另外一个在字典中。如果在未来的某个时候你想要去移除这些排,你需要确保两个引用都不可到达。

  1.  
    var elements = {
  2.  
    button: document.getElementById('button'),
  3.  
    image: document.getElementById('image')
  4.  
    };
  5.  
     
  6.  
    function doStuff() {
  7.  
    image.src = 'http://example.com/image_name.png';
  8.  
    }
  9.  
     
  10.  
    function removeImage() {
  11.  
    //image是body元素的子节点
  12.  
    document.body.removeChild(document.getElementById('image'));
  13.  
     
  14.  
    //这个时候我们在全局的elements对象里仍然有一个对#button的引用。
  15.  
    //换句话说,buttom元素仍然在内存中而且不能被回收。
  16.  
    }

当涉及DOM树内部或子节点时,需要考虑额外的考虑因素。例如,你在JavaScript中保持对某个表的特定单元格的引用。有一天你决定从DOM中移除表格但是保留了对单元格的引用。人们也许会认为除了单元格其他的都会被回收。实际并不是这样的:单元格是表格的一个子节点,子节点保持了对父节点的引用。确切的说,JS代码中对单元格的引用造成了 整个表格被留在内存中了 ,所以在移除有被引用的节点时候要当心。

我们在SessionStack努力遵循这些最佳实践,因为:

一旦你整合essionStack到你的生产应用中,它就开始记录所有的事情:DOM变化、用户交互、JS异常、堆栈跟踪、失败的网络请求、调试信息,等等。

通过SessionStack,你可以回放应用中的问题,看到问题对用户的影响。所有这些都不会对你的应用产生性能的影响。因为用户可以重新加载页面或者在应用中跳转,所有的观察者、拦截器、变量分配都必须合理处置。以免造成内存泄漏,也预防增加整个应用的内存占用。

JavaScript:4个常见的内存泄露的更多相关文章

  1. JavaScript 中 4 种常见的内存泄露陷阱

    了解 JavaScript 的内存泄露和解决方式! 在这篇文章中我们将要探索客户端 JavaScript 代码中常见的一些内存泄漏的情况,并且学习如何使用 Chrome 的开发工具来发现他们.读一读吧 ...

  2. JavaScript 中常见的内存泄露陷阱(摘)

    内存泄露是每个开发者最终都不得不面对的问题.即便使用自动内存管理的语言,你还是会碰到一些内存泄漏的情况.内存泄露会导致一系列问题,比如:运行缓慢,崩溃,高延迟,甚至一些与其他应用相关的问题. 什么是内 ...

  3. 谈谈.NET中常见的内存泄露问题——GC、委托事件和弱引用

    其实吧,内存泄露一直是个令人头疼的问题,在带有GC的语言中这个情况得到了很大的好转,但是仍然可能会有问题.一.什么是内存泄露(memory leak)?内存泄露不是指内存坏了,也不是指内存没插稳漏出来 ...

  4. .NET中常见的内存泄露问题——GC、委托事件和弱引用

    一.什么是内存泄露(memory leak)? 内存泄露不是指内存坏了,也不是指内存没插稳漏出来了,简单来说,内存泄露就是在你期待的时间内你程序所占用的内存没有按照你想象中的那样被释放. 因此什么是你 ...

  5. 对开发中常见的内存泄露,GDI泄露进行检测

    对开发中常见的内存泄露,GDI泄露进行检测 一.GDI泄露检测方法: 在软件测试阶段,可以通过procexp.exe 工具,或是通过任务管理器中选择GDI对象来查看软件GDI的对象是使用情况. 注意点 ...

  6. 分享.net常见的内存泄露及解决方法

    分享.net常见的内存泄露及解决方法 关于内存泄漏的问题,之前也为大家介绍过,比如:<C++中内存泄漏的检测方法介绍>,是关于C++内存泄漏的.今天为大家介绍的是关于.NET内存泄漏的问题 ...

  7. Andorid 内存溢出与内存泄露,几种常见导致内存泄露的写法

    内存泄露,大部分是因为程序的逻辑不严谨,但是又可以跑通顺,然后导致的,内存溢出不会报错,如果不看日志信息是并不知道有泄露的.但是如果一直泄露,然后最终导致的内存溢出,仍然会使程序挂掉.内存溢出大部分是 ...

  8. Android开发中常见的内存泄露案例以及解决方法总结

    1.单例模式引起的内存泄露 由于单例模式的静态特性,使得它的生命周期和我们的应用一样长,如果让单例无限制的持有Activity的强引用就会导致内存泄漏如错误代码示例: public class Use ...

  9. Android下常见的内存泄露 经典

    转自:http://www.linuxidc.com/Linux/2011-10/44785.htm 因为Android使用Java作为开发语言,很多人在使用会不注意内存的问题. 于是有时遇到程序运行 ...

  10. JavaScript 的垃圾回收与内存泄露

    JavaScript采用垃圾自动回收机制,运行时环境会自动清理不再使用的内存,因此javascript无需像C++等语言一样手动释放无用内存. 在这之前先说一下垃圾回收的两种方式:引用计数与标记清除. ...

随机推荐

  1. SpringBoot web获取请求数据【转】

    SpringBoot web获取请求数据 一个网站最基本的功能就是匹配请求,获取请求数据,处理请求(业务处理),请求响应,我们今天来看SpringBoot中怎么获取请求数据. 文章包含的内容如下: 获 ...

  2. Django学习之路由分发和反向解析

    原 Django学习之路由分发和反向解析 2018年07月12日 14:04:55 huangql517 阅读数 519 1>路由分发 我们之前学习的路由配置都是在项目的全局控制文件(项目名称目 ...

  3. 廖雪峰Python总结3

    1.模块简介 为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件中,这样每个文件包含的代码相对来说就比较少.一个.py文件就称之为一个模块(Module). 使用模块的好处: 提高了代码的可 ...

  4. kubernetes1.4新特性:增加新的节点健康状况类型DiskPressure

    背景资料 在Kubernetes架构图中可以看到,节点(Node)是一个由管理节点委托运行任务的worker. 它能运行一个或多个Pods,节点(Node)提供了运行容器环境所需要的所有必要条件,在K ...

  5. 【JZOJ4761】【NOIP2016提高A组模拟9.7】鼎纹

    题目描述 输入 输出 样例输入 2 3 4 4 2 1100 0110 1100 10 01 10 00 2 2 2 2 11 11 01 10 样例输出 YES NO 数据范围 解法 由于鼎纹中的第 ...

  6. sql标签和include标签的使用

    <sql>用来封装SQL语句, <include>用来调用<sql>封装的语句 代码片段: <sql id="select"> SE ...

  7. QT 引用之前项目模板导致计算速度严重下降

    最近做RRT规划算法,在新建工程中测试时,每一个周期大概花费20MS,但是当我把算法移植到之前写的工程模板中时,计算效率相当低,变为500毫秒.期初是以为代码有问题,然后就逐句查找,发现代码并没有问题 ...

  8. 邀您共赴数据库学术顶会ICDE 2019——阿里云专场 零距离接触达摩院数据库“最强大脑”

    摘要: 当学术大家遇到技术大拿,会碰撞出怎样的火花?为进一步加深产学研学术交流,阿里云将于ICDE 2019大会期间(4月9日)举办以“云时代的数据库”为主题的技术专场(Workshop) 作为全球数 ...

  9. compass与css sprite(雪碧图)

    什么是css sprite? css sprite,中文叫雪碧图,也有人喊CSS精灵,就是一种背景拼合的技术,然后通过background-position来显示雪碧图中需要显示的图像. MDN相关链 ...

  10. Flutter SDK path为空导致工程打开后不显示iOS模拟器的问题

    说明下问题场景,面向git编程时下载了个开源的Flutter项目 Mac系统下AndroidStudio打开工程后,发现顶部不展示iPhone模拟器 根据ide浅黄色提示提示,判断是FlutterSD ...