导致JS内存泄漏的几种情况

来源:互联网 发布:数据库系统基础知识 编辑:程序博客网 时间:2024/06/15 12:50

内存泄漏是开发中很常见的问题,即使使用具有自动管理内存的语言,也有可能出现内存泄漏的情况,内存泄漏可能会引起变慢、延迟、崩溃等问题。

要解决内存泄漏问题,首先要弄懂什么是内存泄漏,什么情况下会导致内存泄漏。这样,当出现内存泄漏时才知道如何应对。

什么是内存泄漏?

内存泄漏是指不再用到的内存,没有及时释放。既不能使用,又不能回收。

程序的运行需要内存。对于持续运行的进程,如果不及时释放不再用到的内存,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。

要了解 JS 内存泄漏的几种情况,我们首先来了解一下 JS 的内存是如何管理的,即 JS 的垃圾收集机制。

垃圾收集机制

Javascript 具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中的使用的内存。所需内存的分配 以及无用的回收 完全实现了自动管理。

JavaScript 垃圾回收机制很简单:找出不再使用的变量,然后释放掉其占用的内存。但是这个过程不是时时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。

JavaScript 中最常用的垃圾收集方式有 2 种:标记清除引用计数

1)“标记清除”——当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。当变量离开环境时,这将其 标记为“离开环境”。

从逻辑上讲,永远不能释放进入环境的变量所占的内存,因为只要执行流进入相应的环境,就可能用到它们。

function fun(){    var a = 1;//被标记 进入环境    var b= 2;//被标记 进入环境}fun();//执行完毕之后,a,b 被标记离开环境,被回收

如果我们的代码写法不当,会让变量一直处于“进入环境”的状态,无法被回收。

2)“引用计数”——语言引擎有一张”引用表”,跟踪记录每个值被引用的次数
如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放。

如果一个值不再需要了,引用数却不为0,垃圾回收机制无法释放这块内存,从而导致内存泄漏。

那些很占空间的值,一旦不再用到,你必须检查是否还存在对它们的引用。如果是的话,就必须手动解除引用

function test(){ var a = {} ; //a的引用次数为0  var b = a ; //a的引用次数加1,为1  var c = a; //a的引用次数再加1,为2 var b ={}; //a的引用次数减1,为1}

导致内存泄漏的几种情况

1. 意外的全局变量

function leaks(){      leak = 'xxxxxx';//leak 成为一个全局变量,不会被回收}

调用完函数以后,变量仍然存在,导致泄漏.
你可以通过加上 ‘use strict’ 启用严格模式来避免这类问题, 严格模式会阻止你创建意外的全局变量.

2. 闭包

闭包可以维持函数内局部变量,使其得不到释放。
解决办法:在函数外部定义事件处理函数,解除闭包。或在闭包中,删除没用的属性以减少对内存的消耗。或在外部函数中删除对DOM的引用

function bindEvent() {     var obj=document.createElement("XXX");     obj.onclick=function(){         //Even if it's a empty function     }     obj=null; }

3. 未清除 dom 元素的引用

dom 元素移除,但 对 dom 元素的引用没有解除,会导致内存泄漏。
解决办法:手工移除。

var elements = {    button: document.getElementById('button'),    image: document.getElementById('image')}function doStuff() {    image.src = 'http://some.url/image';    button.click();    console.log(text.innerHTML);}function removeButton() {    document.body.removeChild(document.getElementById('button'));  // 虽然我们用removeChild移除了button, 但是还在elements对象里保存着#button的引用 // 换言之, DOM元素还在内存里面.}

4.循环引用

循环引用 在引用计数策略下会导致内存泄漏,标记清除不会。
解决办法:手工解除循环引用。

function fn() { var a = {}; var b = {}; a.pro = b; b.pro = a;} fn();

a和b的引用次数都是2,fn()执行完毕后,两个对象都已经离开环境。
在标记清除方式下是没有问题的,但是在引用计数策略下,a和b的引用次数不为0,不会被垃圾回收器回收内存。如果fn函数被大量调用,就会造成内存泄漏。

IE中的BOM和DOM中的对象使用C++以COM(component Object Model,组件对象模型)对象的形式实现而COM对象的垃圾收集机制采用的是引用计数策略。换句话说,只要在IE中涉及COM对象,就会存在循环引用的问题。

var element = document.getElementById("some_element");var myObject = new Object();myObject.e = element;element.o = myObject;//手工断开它们之间的连接myObject.element=null;element.someObject=null;

5. 被遗忘的计时器或回调

var someResource = getData();setInterval(function() {    var node = document.getElementById('Node');    if(node) {        node.innerHTML = JSON.stringify(someResource));    }}, 1000);

如果 id 为 Node 的元素从 DOM 中移除, 该定时器仍会存在, 同时, 因为回调函数中包含对 someResource 的引用, 定时器外面的 someResource 也不会被释放.

原创粉丝点击