js 内存泄漏

在javascript中,我们很少去关注内存的管理。我们创建变量,使用变量,浏览器关注这些底层的细节都显得很正常。

但是当应用程序变得越来越复杂并且ajax化之后,或者用户在一个页面停留过久,我们可能需要去注意一些问题,如一个浏览器花费了1G以上的内存,并且在不断的增加。这些问题常常都是因为内存泄露引起。

Javascript 内存泄露

这个javascript内存管理的核心概念就是具不具有可达性的概念。

1 一个明显的对象集合将会被认为是可达的:这些对象是被知道的像roots一样。

包括那些所有的对象在堆栈中个地方被引用(包括,所有的局部变量,正在被调用的方法的中的参数),以及任何的全局变量。

2 对象保存在内存中,他们是可以到达的从roots 对象,通过一个引用货者一个引用链。

这里有一个GC 垃圾回收器在浏览器中,用来清楚没有用的对象在内存中。

垃圾回收example

?


1

2

3

4

5

6

7

8

9

10

function Menu(title) {

  this.title = title

  this.elem = document.getElementById(‘id‘)

}

var menu = new Menu(‘My Menu‘)

document.body.innerHTML = ‘‘  // (1)

menu = new Menu(‘His menu‘) // (2)

来看一下内存结构:

在step(1) 中,Body.innerHTML 被清除掉,所以它的子节点也会被删除,因为他们不再被关联。

但是这个元素#id 是一个例外,他是是被 menu.elem 关联着,所以该对象会一直存在内存中,当然 ,如果你检查他的parentNode, 将会得到一个null值。

注意:个别的Dom元素 可以会保存在内存中即使他们的parent 被移除了。

在step(2) 中,引用window.menu 被定义,所以之前的 menu因为不再被关联,它将会自动被移除通过浏览器的GC。

循环引用集合

闭包经常会导致循环引用,例如:

?


1

2

3

4

5

6

7

8

9

function setHandler() {

  var elem = document.getElementById(‘id‘)

  elem.onclick = function() {

    // ...

  }

}

在这里,这个DOM 元素直接引用匿名function通过onclick。并且这个function引用了elem元素通过外部的词法环境。

( 这里多说一点,关于[[Scope]]是function的内部属性,在创建function的时候,会将外部函数的词法环境加入到[[Scope]]中,这里涉及到javascript的作用域问题。)

这样的内存结构一样会出现即使这个处理函数内部没有任何的代码。特别的一些方法如addEventListener/attachEvent 也会在内部创建一个引用。

在这个处理函数中通常进行清除,当这个elem死亡的时候。

?


1

2

3

4

function cleanUp() {

  var elem = document.getElementById(‘id‘)

  elem.parentNode.removeChild(elem)

}

调用clearUp删除元素从Dom 中。这里依旧存在一个引用,LexialEnvironment.elem ,但是这里没有了嵌套的functions,所以 LexialEnvironment 是可以回收的。

在这之后,elem 变成没有关联的并且和他的handlers一起被回收。

内存泄露

内存泄露主要发生当一些浏览器由于一些问题不能够移除没有用的对象从内存中。

这发生可能是由于一些原因,如浏览器的Bugs,浏览器的扩展问题,或多或少,我们自己的代码错误。

IE 8 以下 DOM-JS 内存泄露

IE8 之前的浏览器不能对DOM和javascript之间的循环引用进行清理。这个问题相对更加的严重在ie6 windows xp sp3 之前的版本

因为内存没法释放在页面卸载之前。

所以 setHandler 泄露在ie 8 之前的浏览器,elem 和这些闭包没办法清除。

?


1

2

3

4

function setHandler() {

  var elem = document.getElementById(‘id‘)

  elem.onclick = function() { /* ... */ }

}

不仅仅是DOM 元素,包括XMLHttpRequest 或者其它COM 对象,都会存在此现象。

在IE下用来打破循环引用的方法:

我们定义了elem = null,所以这个处理函数不再关联到DOM 元素,这个循环自然打破。

XmlHttpRequest 内存管理和泄露

下面的代码在i9以下浏览器内存泄露:

?


1

2

3

4

5

6

7

8

9

10

11

var xhr = new XMLHttpRequest() // or ActiveX in older IE

xhr.open(‘GET‘, ‘/server.url‘, true)

xhr.onreadystatechange = function() {

  if(xhr.readyState == 4 && xhr.status == 200) {           

    // ...

  }

}

xhr.send(null)

看一下内存结构:

这个异步xmlHttpRequest对象一直被浏览器追踪,因为有一个内部的引用关联到它。

当这个请求结束之后,这个引用就会被删除,所以xhr 变成不可关联对象。但是ie9以下的浏览器不是这么做的。

幸运的是,要修复这个Bug很简单,我们需要删除这个xhr 从这个闭包中并且使用它用this在这个处理函数中。如下:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

var xhr = new XMLHttpRequest()

  

xhr.open(‘GET‘, ‘jquery.js‘, true)

  

xhr.onreadystatechange = function() {

  if(this.readyState == 4 && this.status == 200) {           

    document.getElementById(‘test‘).innerHTML++

  }

}

   

xhr.send(null)

xhr = null

}, 50)

这样就没有了循环引用。

setInterval/setTimeout

在使用setTimeout/setInterval 也会存在内部引用并且被追踪知道结束,然后clear up.

对于setInterval 这个结束发生在 clearInterval中,这个可能会导致内存泄露当这个方法实际什么也 没做,但是这个interval却没有被清除。

内存泄露的大小

内存泄露的数据结构的size可能不大。

但是这个闭包会导致外部函数的所有的变量遗留下来,当这个内部函数是活动的时候。

所以,你可以想象,你创建了一个function,而且其中一个变量包含了一个大的字符串。

?


1

2

3

4

5

6

7

8

9

10

11

function f() {

  var data = Large piece of data, probably received from server

  /* do something using data */

  function inner() {

    // ...

  }

  return inner

}

While the function inner function stays in memory, then the LexicalEnvironment with a large variable inside will hang in memory until the inner function is alive.

事实上,这可能没有泄露,许多的fucntions 可能会被创建因为一些合理的原因。比如,对于每一个请求,并不清干净,因为他们是一些处理函数或者其它什么。

如果这个data 仅仅被使用在外部函数,我们可以使它作废在外部方法中。

?


1

2

3

4

5

6

7

8

9

10

11

12

13

function f() {

  var data = Large piece of data, probably received from server

  /* do something using data */

  function inner() {

    // ...

  }

data = null

  return inner

}

现在。这个data 依旧保留在内存中作为一个词法环境的一个属性,不过它不再需要去占用太多的空间。

jQuery 内存泄露和避免方式

jQuery 使用 $.data 去避免ie 6 7 内存泄露。不幸运的是,它导致了一些新的 jQuery 特殊的内存泄露。

这个核心原理关于$.data是,任何的javascript实体被限制去读取一个元素使用如下的方式

?


1

2

3

4

// works on this site cause it‘s using jQuery

$(document.body).data(‘prop‘, ‘val‘) // set

alert( $(document.body).data(‘prop‘) ) // get

jQuery $(elem).data(prop,val) 按照如下步骤:

1 元素获取一个唯一的标记如果它不存在的话:

?


1

elem[ jQuery.expando ] = id = ++jQuery.uuid  // from jQuery source

2 data 被设置到一个特殊的对象 jQuery.cache:

?


1

jQuery.cache[id][‘prop‘] = val

当这个date从一个元素中被读取:

1 这个元素的唯一标示会被返回:id = elem[jQuery.expando]

2 这个data 会被读取从jQuery.cache[id]

jQuery设置这个api的目的就是为了让DOM元素不再直接引用Javascript元素。它使用了一个数量,但是很安全。

这个data 保存在jQuery.cache中。内部事件处理函数同样使用$.data API。

同时也造成了另一方面的影响,一个元素不能被移除从DOM中使用 本地的调用。

如下代码造成了内存泄露在所有的浏览器中:

?


1

$(‘

‘) .html(new Array(1000).join(‘text‘)) // div with a text, maybe AJAX-loaded .click(function() { }) .appendTo(‘#data‘) document.getElementById(‘data‘).innerHTML = ‘‘ 
这个泄露的发生因为elem 被removeed 通过清除 parent 的innerHTML .但是这个data依旧保存在jQuery.cache中。

更重要的是,这个事件处理函数引用elem,所以这个事件处理函数和elem保留在内存中和整个闭包。

一个简单的泄露例子

?


1

2

function go() {

  $(‘

‘) .html(new Array(1000).join(‘text‘)) .click(function() { }) } 
这个例子的问题在于,这个元素被创建了,但是没有使用。所以在这个函数定义之后,这个引用就消失了, 但是这个jQuery.cache中依旧是存在的。

时间: 2024-10-09 21:29:58

js 内存泄漏的相关文章

Node.js内存泄漏分析

在极客教育出版了一个视频是关于<Node.js 内存泄漏分析>,本文章主要是从内容上介绍如何来处理Node.js内存异常问题.如果希望学习可前往极客学院: 本文章的关键词 - 内存泄漏 - 内存泄漏检测 - GC分析 - memwatch 文章概要 由于内存泄漏在Node.js中非常的常见,可能在浏览器中应用javascript时,对于其内存泄漏不是特别敏感,但作为服务器语言运行时,你就不得不去考虑这些问题.由于很小的逻辑可能导致服务器运行一天或者一个星期甚至一个月才会让你发现内存不断上涨,而

JS内存泄漏 和Chrome 内存分析工具简介(摘)

原文地址:http://web.jobbole.com/88463/ JavaScript 中 4 种常见的内存泄露陷阱 原文:Sebastián Peyrott 译文:伯乐在线专栏作者 - ARIGATO 链接:http://web.jobbole.com/88463/ 点击 → 了解如何加入专栏作者 了解 JavaScript 的内存泄露和解决方式! 在这篇文章中我们将要探索客户端 JavaScript 代码中常见的一些内存泄漏的情况,并且学习如何使用 Chrome 的开发工具来发现他们.读

js内存泄漏

关于内存泄漏的基本概念可以参考以下两篇文章: http://boke.io/javascriptnei-chang-jian-nei-cun-xie-lou-yuan-yin/ http://boke.io/gao-ding-nei-cun-xie-lou/ 其中内容讲到dom情况引起的泄漏,讲的不够详细,由于原文代码引入了zepto,而在zepto绑定事件的时候,会产生闭包保存了对原dom元素的引用,导致即使将dom清空,事件如果不off的话,dom还是不会回收. 重复清空的结果,会导致dom

Js内存泄漏的几种情况

想解决内存泄露问题,必须知道什么是内存泄露,什么情况下出现内存泄露,才能在遇到问题时,逐个排除.这里只讨论那些不经意间的内存泄露. 一.什么是内存泄露 内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束.在C++中,因为是手动管理内存,内存泄露是经常出现的事情.而现在流行的C#和Java等语言采用了自动垃圾回收方法管理内存,正常使用的情况下几乎不会发生内存泄露.浏览器中也是采用自动垃圾回收方法管理内存,但由于浏览器垃圾回收方法有bug,会产生内存泄露. 二.内存泄露的几种情况

js晋级篇——前端内存泄漏探讨

1.IE7/8 DOM对象或者ActiveX对象循环引用导致内存泄漏 循环引用分为两种: 第一种:多个对象循环引用 var a=new Object; var b=new Object; a.r=b; b.r=a; 第二种:循环引用自己 var a=new Object; a.r=a; 对于ECMAScript 对象而言,只要没有其他对象引用对象 a.b,也就是说它们只是相互之间的引用,那么仍然会被垃圾收集系统识别并处理. 但是,在 IE7.IE8 中,如果循环引用中的任何对象是 DOM 节点或

如何定位 Node.js 的内存泄漏

基础知识 Node.js 进程的内存管理,都是有 V8 自动处理的,包括内存分配和释放.那么 V8 什么时候会将内存释放呢? 在 V8 内部,会为程序中的所有变量构建一个图,来表示变量间的关联关系,当变量从根节点无法触达时,就意味着这个变量不会再被使用了,就是可以回收的了.而这个回收是一个过程性的,从快速 GC 到 最后的 Full GC,是需要一段时间的.另外,Full GC 是有触发阈值的,所以可能会出现内存长期占用在一个高值,也可以算是一种内存泄漏,可以从<一次 Node.js 应用内存暴

关于js闭包是否真的会造成内存泄漏(转载)

闭包是一个非常强大的特性,但人们对其也有诸多无解.一种危言耸听的说法是闭包会造成内存泄露. 局部变量本来应该在函数退出的时候被解除引用,但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直生存下去.从这个意义上看,闭包的确会使一些数据无法被及时销毁.使用闭包的一部分原因是我们选择主动把一些变量封存在闭包中,因为可能在以后还需要使用这些变量,把这些变量放在闭包中和放在全局作用域,对内存方面的影响是一致的,这里并不能说成是内存泄露.如果在将来需要回收这些变量,我们可以手动把这些变量设为n

转《js闭包与内存泄漏》

首先,能导致内存泄漏的一定是引用类型的变量,比如函数和其他自定义对象.而值类型的变量是不存在内存泄漏的,比如字符串.数字.布尔值等.因为值类型是靠复制来传递的,而引用类型是靠类似c语言中的指针来传递的.可以认为一个引用类型的变量就是一个指向某个具体的内存地址的指针. 当我们用js代码创建一个引用类型的时候(以下简称对象),js引擎会在内存中开辟一块空间来存放数据,并把指针引用交给那个变量.内存是有限的,js引擎必须保证当开辟的对象没用的时候,把所分配的内存空间释放出来,这个过程叫做垃圾回收,负责

【JS】----垃圾回收----内存泄漏

内存的生命周期: 分配你所需要的内存: 由于字符串.对象等没有固定的大小,js程序在每次创建字符串.对象的时候,程序都会分配内存来存储那个实体. 使用分配到的内存做点什么. 不需要时将其释放回归: 在不需要字符串.对象的时候,需要释放其所占用的内存,否则将会消耗完系统中所有可用的内存,造成系统崩溃,这就是垃圾回收机制所存在的意义. 所谓的内存泄漏指的是:由于疏忽或错误造成程序未能释放那些已经不再使用的内存,造成内存的浪费. 垃圾回收机制: 在C和C++之类的语言中,需要手动来管理内存的,这也是造