在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. Orchard运用 - 导入旧随笔导致归档的问题

    归档功能对于一个博客App这应该必须有的需求,故此Orchard中博客模块默认实现这一特性并通过一个叫"Blog Archives" widget, 你可以将其安装博客主页的侧边栏 ...

  2. Linux下读取RFID卡号(C串口编程)

    由于项目需要用到RFID.GPRS.摄像头等模块所以便看了一下,整理了一下学习思路,本篇先是整理一下串口读取RFID卡号的程序思路,后面还会更其他的 RFID模块: 本次采用的是125K的RFID读卡 ...

  3. Tensorflow 之 name/variable_scope 变量管理

    name/variable_scope 的作用 充分理解 name / variable_scope TensorFlow 入门笔记 当一个神经网络比较复杂.参数比较多时,就比较需要一个比较好的方式来 ...

  4. 如何获得Windows Server 2012上的FC的WWN

    我有一个测试环境, 一对Host与storage array之间既有iSCSI的连接, 也有FC的连接. 原来iSCSI的连接是OK的, 现在需要转用FC. 当在光纤交换机上划好了Zone之后, st ...

  5. 国内站点经常使用的一些 CDN 静态资源公共库加速服务

    web开发人员们的福利来了..旨在为大家提供更快很多其它更好的静态资源库的CDN载入库方案! CDN公共库是指将经常使用的JS库存放在CDN节点,以方便广大开发人员直接调用. 与将JS库存放在serv ...

  6. 数组根据index拆分和查询下标

    private class ArrayTool<T> { /// <summary>查询值在数组中的下标</summary> /// <param name= ...

  7. Iocomp控件教程之Pie Chart——饼状图控件

    Pie Chart--饼状图控件(Pie Chart)以饼状图形式显示每一个项目内容所占的百分比比重.在设计时.能够使用属性编辑器加入或者移除项目以及更改属性值.在执行时.使用AddItem,Remo ...

  8. Graph Automata Player

    题目action=problem&type=show&id=12839&courseid=269">here 第一道高速幂.同一时候也是第一道高斯消元. 输入的 ...

  9. 在Flex (Flash)中嵌入HTML 代码或页面—Flex IFrame

    在flex组件中嵌入html代码,可以利用flex iframe.这个在很多时候会用到的,有时候flex必须得这样做,如果你不这样做还真不行…… flex而且可以和html进行JavaScript交互 ...

  10. 算法笔记_146:TarJan算法的应用(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 Problem Description 为了训练小希的方向感,Gardon建立了一座大城堡,里面有N个房间(N<=10000)和M条通道(M& ...