在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中依旧是存在的。


js 内存泄漏的更多相关文章

  1. Chrome JS内存泄漏排查方法(Chrome Profiles)

     原文网址:http://blog.csdn.net/kaitiren/article/details/19974269 JS内存泄漏排查方法(Chrome Profiles)   Google Ch ...

  2. js内存泄漏

    IE和webkit浏览器都是采用计数来处理垃圾,也就是说每个对象被引用一次,该对象的计数器成员+1,如果计数器为0,那么这个对象被销毁 例如: function A() { var obj = {}; ...

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

    原文地址:http://web.jobbole.com/88463/ JavaScript 中 4 种常见的内存泄露陷阱   原文:Sebastián Peyrott 译文:伯乐在线专栏作者 - AR ...

  4. JS内存泄漏排查方法——Chrome Profiles

    一.概述 Google Chrome浏览器提供了非常强大的JS调试工具,Heap Profiling便是其中一个.Heap Profiling可以记录当前的堆内存(heap)快照,并生成对象的描述文件 ...

  5. JS内存泄漏排查方法-Chrome Profiles

    原文链接:http://caibaojian.com/chrome-profiles.html 一.概述 Google Chrome浏览器提供了非常强大的JS调试工具,Heap Profiling便是 ...

  6. [转]JS内存泄漏排查方法(Chrome Profiles)

    Google Chrome浏览器提供了非常强大的JS调试工具,Heap Profiling便是其中一个.Heap Profiling可以记录当前的堆内存(heap)快照,并生成对象的描述文件,该描述文 ...

  7. 写出js内存泄漏的问题?

    回答一: (1)IE7/8 DOM对象或者Active对象循环引用导致内存泄漏 a.多个对象循环引用 b.循环的DOM泄漏 (2)基础的DOM泄漏 当原有的DOM被移除时,子节点引用没有被移除则无法回 ...

  8. js内存泄漏的问题?

    内存泄漏指任何对象在您不再拥有或需要它之后仍然存在. 垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量.如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环 ...

  9. Js内存泄漏的几种情况

    想解决内存泄露问题,必须知道什么是内存泄露,什么情况下出现内存泄露,才能在遇到问题时,逐个排除.这里只讨论那些不经意间的内存泄露. 一.什么是内存泄露 内存泄露是指一块被分配的内存既不能使用,又不能回 ...

随机推荐

  1. PowerDesigner教程系列(三)概念数据模型

    目标: 本文主要介绍属性的标准检查约束.如何定义属性的附加检查. 一.定义属性的标准检查约束 标准检查约束是一组确保属性有效的表达式.在实体属性的特性窗口,打开如图所示的检查选项卡. 在这个选项卡可以 ...

  2. 性能调优的Windows窗体DataGridView控件

    性能调优的Windows窗体DataGridView控件 . 净框架4.5     在处理大量数据时, DataGridView 控制可以消耗大量的内存开销,除非你仔细地使用它. 在客户有限的内存,你 ...

  3. 推荐8款HTML5相关的特殊效果

    HTML5是HTML的升级版,HTML5有两大特点:首先,强化了 Web 网页的表现性能.其次,追加了本地数据库等 Web 应用的功能.广义论及HTML5时,实际指的是包括HTML.CSS和JavaS ...

  4. C#应用视频教程3.2 Halcon软件测试

    Halcon是图像处理比较牛逼的一个软件(德国人的东西,做的非常强大,里面集成的算法很多,可能你一辈子研究这个也做不过他),然而牛逼归牛逼,康耐视,基恩士也很牛逼,但是日本人的东西一般太过封闭,屌丝用 ...

  5. Axure与iPhone应用程序原型创建(五)

    在设计iPhone应用程序原型时,常常需要通过一个滑动的动画从一个屏幕切换到另一个屏幕.使用Axure RP进行设计时,可以将屏幕设计到动态面板里面,通过设置面板状态事件来实现.在下文中作者将通过一个 ...

  6. 一次寻找IBatisNet事务bug的过程

    本文的上下文环境 操作系统:Win7 x64 Professional 开发工具:Visual Studio 2017   语言:C# 数据库ORM:IBatisNet 1.6.2 一.前言 这个项目 ...

  7. 多用户角色权限访问模块问题”的解决思路( 位运算 + ActionFilterAttribute )

    如果你还是不太懂位运算,请看我的文章:那些年我们一起遗忘的位运算! 下面是我在这次项目中学习到的,我眼中的位运算的应用!主要是实现 通知的3个操作: 1.  置顶 2.  设为首页 3.  同时为 “ ...

  8. zuul源码分析-探究原生zuul的工作原理

    前提 最近在项目中使用了SpringCloud,基于zuul搭建了一个提供加解密.鉴权等功能的网关服务.鉴于之前没怎么使用过Zuul,于是顺便仔细阅读了它的源码.实际上,zuul原来提供的功能是很单一 ...

  9. Jquery中对checkbox的各种“全选”或者“取消”功能实现(特别注意1.6+的一定不能使用attr来取属性了!用prop!)

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  10. 〖Android〗查找Android中的/system/lib中增加的lib文件是否在apk文件中

    #!/bin/bash - #=============================================================================== # # F ...