什么是内存泄漏

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

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

某些编程语言为开发者提供了释放内存功能。另一些则期待开发者清楚的知道一段内存什么时候是没用的。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. Codeforces Round #410 (Div. 2) A. Mike and palindrome【判断能否只修改一个字符使其变成回文串】

    A. Mike and palindrome time limit per test 2 seconds memory limit per test 256 megabytes input stand ...

  2. Linux与Unix shell编程指南(完整高清版).pdf

    找到一本很详细的Linux Shell脚本教程,其实里面不光讲了Shell脚本编程,还介绍了系统的各种命令 http://vdisk.weibo.com/s/yVBlEojGMQMpv 本书共分五部分 ...

  3. Mybatis中example类的使用

    要使用example类,先要在项目中导入mybatis.mapper的jar包. Mapper接口中包含了单表的增删改查以及分页功能. 给出实例: CountryMappermapper = sqlS ...

  4. javascript正则表达式知识大全

    什么是正则表达式 正则表达式(regular expression)是一个描述字符模式的对象.ECMAScript的RegExp类表示正则表达式,而String和RegExp都定义了使用正则表达式进行 ...

  5. ListView组件中 onEndReached 方法在滚动到距离列表最底部一半时执行

    初次使用ListView,在写列表滚动到最底部自动加载使用到方法onEndReached, 发现: ListView组件中 onEndReached 方法在滚动到距离列表最底部一半时执行, 于是翻看文 ...

  6. iOS学习系列 - 扩展机制category与associative

    iOS学习系列 - 扩展机制category与associative category与associative作为objective-c的扩展机制的两个特性,category即类型,可以通过它来扩展方 ...

  7. celery 动态定时任务探索

    环境: celery 4.3 flask python 3.7 linux 需求: 动态添加定时任务,且方便维护. 解决思路: 参考django-celery 或是celery源码,将定时任务配置放置 ...

  8. @codeforces - 1161F@ Zigzag Game

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定一个 2n 个结点的完全二分图,1~n 在左边,n+1~2n ...

  9. Python基础:15私有化

    默认情况下,属性在Python 中都是“public”. 1:双下划线(__) Python 为类元素(属性和方法)的私有性提供初步的形式.由双下划线开始的属性在运行时被“混淆”,所以直接访问是不允许 ...

  10. AIDL基本用法

    1. AIDL有什么用?用TA到目的是什么? 2. 怎么用AIDL? 1. AIDL有什么用? 1.1. 为了提高代码执行速度,将部分逻辑封入C/C++代码中  1.2. 为了调用这部分代码,使用JN ...