《浏览器内存泄漏》word版.doc_第1页
《浏览器内存泄漏》word版.doc_第2页
《浏览器内存泄漏》word版.doc_第3页
《浏览器内存泄漏》word版.doc_第4页
《浏览器内存泄漏》word版.doc_第5页
已阅读5页,还剩13页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

浏览器内存泄露3.内存泄露的方式目前发现的可能导致内存泄露的代码有三种:循环引用 自动类型装箱转换 某些DOM操作 下面具体的来说说内存是如何泄露的循环引用:这种方式存在于IE6和FF2中(FF3未做测试),当出现了一个含有DOM对象的循环引用时,就会发生内存泄露。什么是循环引用?首先搞清楚什么是引用,一个对象A的属性被赋值为另一个对象B时,则可以称A引用了B。假如B也引用了A,那么A和B之间构成了循 环引用。同样道理 如果能找到A引用B B引用C C又引用A这样一组饮用关系,那么这三个对象构成了循环引用。当一个对象引用自己时,它自己形成了循环引用。注意,在js中变量永远是对象的属性,它可以 指向对象,但决不是对象本身。循环引用很常见,而且通常是无害的,但如果循环引用中包含DOM对象或者ActiveX对象,那么就会发生内存泄露。例子:var a=document.createElement(div);var b=new Object();a.b=b;b.a=a;很多情况下循环引用不是这样的明显,下面就是著名的闭包(closure)造成内存泄露的例子,每执行一次函数A()都会产生内存泄露。试试看,根据前面 讲的scope对象的知识,能不能找出循环引用?function A(). var a=document.createElement(div); a.onclick=function(). alert(hi); A();OK, 让我们来看看。假设A()执行时创建的作用域对象叫做ScopeA 找到以下引用关系ScopeA引用DOM对象document.createElement(div);DOM对象document.createElement(div);引用函数function()alert(hi)函数function()alert(hi)引用ScopeA这样就很清楚了,所谓closure泄露,只不过是几个js特殊对象的循环引用而已。自动类型装箱转换:这种泄露存在于ie6 ie7中。这是极其匪夷所思的一个bug,看下面代码var s=lalalalala;alert(s.length);这段代码怎么了?看看吧,lalalalala已经泄露了。关键问题出在s.length上,我们知道js的类型中,string并非对象,但可以对 它使用.运算符,为什么呢?因为js的默认类型转换机制,允许js在遇到.运算符时自动将string转换为object型中对应的String对象。而 这个转换成的临时对象100%会泄露(汗一下)。某些DOM操作也可能导致泄露 这些恶心的bug只存在于ie系列中。在ie7中 因为试图fix循环引用bug而让情况变得更糟,以至于我对写这一段种满了恐惧。从ie6谈起,下面是微软的例子, . function LeakMemory() . var hostElement = document.getElementById(hostElement); / Do it a lot, look at Task Manager for memory response for(i = 0; i 5000; i+) . var parentDiv = document.createElement(); var childDiv = document.createElement(); / This will leak a temporary object parentDiv.appendChild(childDiv); hostElement.appendChild(parentDiv); hostElement.removeChild(parentDiv); parentDiv.removeChild(childDiv); parentDiv = null; childDiv = null; hostElement = null; function CleanMemory() . var hostElement = document.getElementById(hostElement); / Do it a lot, look at Task Manager for memory response for(i = 0; i 5000; i+) . var parentDiv = document.createElement(); var childDiv = document.createElement(); / Changing the order is important, this wont leak hostElement.appendChild(parentDiv); parentDiv.appendChild(childDiv); hostElement.removeChild(parentDiv); parentDiv.removeChild(childDiv); parentDiv = null; childDiv = null; hostElement = null; Memory Leaking Insert Clean Insert 看看结果吧,LeakMemory造成了内存泄露,而CleanMemory没有,循环引用了么?仔细看看没有。那么是什么问题呢?MS的解释是插入顺 序不对,必须先将父级元素appendChild。这听起来有些模糊,这里给出一个比较恰当的等价描述:永远不要使用DOM节点树之外元素的 appendChild方法。接下来是ie7和ie8 beta 1中运行这段程序,看到什么?没看错吧,2个都泄露了!别急,刷新一下页面就好了。为什么呢?ie7改变了DOM元素的回收方式:在离开页面时回收DOM 树上的所有元素,所以ie7下的内存管理非常简单:在所有的页面中只要挂在DOM树上的元素,就不会泄露,没挂在DOM树上,肯定泄露。所以,ie7中记 住一条原则:在离开页面之前把所有创建的DOM元素挂到DOM树上。接下来谈谈ie7的这个设计吧,坦白的说,这种做法纯粹是偷懒的垃圾做法。动态垃圾回收不是保证所有内存都在离开页面时收回,而是要保证内存的充分 利用,运行时不回收,等到离开时回收有什么用?这只是名义上的避免泄露,其实是完全的泄露。况且还没有回收DOM节点树之外的元素。4.内存泄露的解决方案内存泄露怎么办?真的以后不用闭包了么?没法封装控件了?这样做还不如要了js程序员的命,嘿嘿。事实上,通过一些很简单的小技巧,可以巧妙的绕开这些危险的bug。4.内存泄露的解决方案显式类型转换 首先说说最容易处理的情况 对于类型转换造成的错误,我们可以通过显式类型转换来避免:var s=newString(lalalalala);/此处将string转换成objectalert(s.length);这个太容易了,算不上正经方案。不过类型转换泄露也就这一种处理方法了。避免事件导致的循环引用 在比较成熟的js程序员里,把事件函数写成闭包是再正常不过了:function A() var a=document.createElement(div); a.onclick=function() alert(hi); 这将导致内存泄露。按照IBM那两位老大的说法,当然是把函数放外面或者a=null就没问题了,不过还要访问A()里面的变量呢?假如有下面的代码:function A() var a=document.createElement(div); var b=document.createElement(div); a.onclick=function() alert(b.outerHTML); return a;如何将它的逻辑表达出来 还避免内存泄露? 分析一下这个内存泄露的形式:只要onclick的外部环境中不包含a那么,就不会泄露。那么办法有2个一是将环境到a的引用断开 另一个是将function到环境的引用断开,但是,如果要在函数中访问b就不能将Function放到外面,如果要返回a的值,就不能a=null,怎 么办呢?解决方案1:构造一个不含a的新环境function A() var a=document.createElement(div); var b=document.createElement(div); a.onclick=BuildEvent(b); return a;function BuildEvent(b) return function() alert(b.outerHTML); a本身可以通过this访问,将其它需要访问的外层函数变量传递给BuildEvent就可以了。保持BuildEvent定义和调用的参数名一致,会带 来方便。解决方案2:在return 之后a=null,不可能? 看看下面:function A() try var a=document.createElement(div); var b=document.createElement(div); a.onclick= function() alert(b.outerHTML); return a; finally a=null; finally在try之后执行,如果finall块不返回值,才会返回try块的返回值。延迟appendChild 还记得函数的lazy initalize吧,对于ie恶心至极的DOM操作泄露,我们需要用类似的方法去处理。在一个函数中构造一个复杂对象,在需要的时候将之 appendChild到DOM树上,这是很常见的做法,但在IE6中,这样做将导致所谓的插入顺序内存泄露,没有别的办法,我们只能用一个数组 parts保存子节点,编写一个appendTo方法先序遍历节点树,去把它挂在某个DOM节点上。function appendTo(Element). Element.appendChild(this); if(!this.parts)return; for(var i=0;ithis.parts.length;i+) parts.appendTo(this);垃圾箱 对于ie7,我比较无可奈何,因为DOM对象不会被CG程序回收,只有离开页面时会被回收,所以我的建议是:使用DOM要有节制,尽量多用 innerHTML吧. good luck.一旦你使用了DOM对象,千万不要试图o=null,你可以设置一个叫做Garbage的div并且将其display设置为none,将不用的 DOM对象存入其中(就是appendChild上去)就好了代理对象 这是Ext的做法,这里只是顺带提一下。将每个元素用一个代理对象操作,不论appendChild还是其他操作都不是对DOM对象本身的操作,而是 通过这个代理对象操作。这是一个很不错的Proxy模式,不过要想避免泄露还是需要一点功夫的,并非用了Proxy之后就不会泄露,有时反而更容易泄露。5 .内存泄露是内存占用很大么? 不是,即使1byte内存也叫做内存泄露。 程序中提示,内存不足,是内存泄露么?不是,这一般是无限递归函数调用导致栈内存溢出。 内存泄露是哪个区域泄露?堆区,栈区是不会泄露的。 window对象是DOM对象么?不是,window对象参与的循环引用不会内存泄露。 内存泄露后果是什么?大多数时候后果不很严重,但过多DOM操作会导致网页执行变慢。 跳转页面后,内存泄露仍然存在么?仍然存在,直到关闭浏览器。 FireFox也会内存泄露么?FF2仍然有内存泄露常规循环引用 内存泄漏和Closure内存泄漏 要了解javascript的内存泄漏问题,首先要了解的就是javascript的GC原理。我记得原来在犀牛书JavaScript: The Definitive Guide中看到过,IE使用的GC算法是计数器,因此只碰到循环 引用就会造成memory leakage。后来一直觉得和观察到的现象很不一致,直到看到Eric的文章,才明白犀牛书的说法没有说得很明确,估计该书成文后IE升级过算法吧。 在IE 6中,对于javascript object内部,jscript使用的是mark-and-sweep算法,而对于javascript object与外部object(包括native object和vbscript object等等)的引用时,IE 6使用的才是计数器的算法。Eric Lippert在/ericlippert/archive/2003/09/17/53038.aspx一文中提到 IE 6中JScript的GC算法使用的是nongeneration mark-and-sweep。对于javascript对算法的实现缺陷,文章如是说:The benefits of this approach are numerous, but the principle benefit is that circular references are not leaked unless the circular reference involves an object not owned by JScript. 也就是说,IE 6对于纯粹的Script Objects间的Circular References是可以正确处理的,可惜它处理不了的是JScript与Native Object(例如Dom、ActiveX Object)之间的Circular References。所以,当我们出现Native对象(例如Dom、ActiveX Object)与Javascript对象间的循环引用时,内存泄露的问题就出现了。当然,这个bug在IE 7中已经被修复了/blog/archives/2006/04/ie_7_and_javasc.html。/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp 中有个示意图和简单的例子体现了这个问题: var myGlobalObject; function SetupLeak() / 产生循环引用,因此会造成内存泄露 / Firstsetupthescriptscopetoelementreference myGlobalObject = document.getElementById( LeakedDiv ); / Nextsetuptheelementtoscriptscopereference document.getElementById( LeakedDiv ).expandoProperty = myGlobalObject; function BreakLeak() / 解开循环引用,解决内存泄露问题 document.getElementById( LeakedDiv ).expandoProperty = null ; 上面这个例子,看似很简单就能够解决内存泄露的问题。可惜的是,当我们的代码中的结构复杂了以后,造成循环引用的原因开始变得多样,我们就没法那么容易观 察到了,这时候,我们必须对代码进行仔细的检查。尤其是当碰到Closure,当我们往Native对象(例如Dom对象、ActiveX Object)上绑定事件响应代码时,一个不小心,我们就会制造出Closure Memory Leak。其关键原因,其实和前者是一样的,也是一个跨javascript object和native object的循环引用。只是代码更为隐蔽,这个隐蔽性,是由于javascript的语言特性造成的。但在使用类似内嵌函数的时候,内嵌的函数有拥有一 个reference指向外部函数的scope,包括外部函数的参数,因此也就很容易造成一个很隐蔽的循环引用,例如:DOM_Node.onevent -function_object. scope -scope_chain -Activation_object.nodeRef -DOM_Node。/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp有个例子 极深刻地显示了该隐蔽性: function AttachEvents(element) / ThisstructurecauseselementtorefClickEventHandler/element 有个引用指向函数ClickEventHandler() element.attachEvent( onclick ,ClickEventHandler); function ClickEventHandler() / Thisclosurerefselement/该 函数有个引用指向AttachEvents(element)调用Scope,也就是执行了参数element。 function SetupLeak() / Theleakhappensallatonce AttachEvents(document.getElementById( LeakedDiv ); 还有这个例子在IE 6中同样原因会引起泄露function leakmaybe()var elm = document.createElement( DIV );elm.onclick = function ()return 2 + 2 ;for ( var i = 0 ;i 10000 ;i + )leakmaybe();btw:关于Closure的知识,大家可以看看/faq/faq_notes/closures.html这篇文章, 习惯中文也可以看看zkjbeyond的blog,他对Closure这篇文章进行了简要的翻译:/zkjbeyond/archive/2006/05/19/47025.html。 之所以会有这一系列的问题,关键就在于javascript是种函数式脚本解析语言,因此javascript中“函数中的变量的作用域是定义作用域,而 不是动态作用域”,这点在犀牛书JavaScript: The Definitive Guide中的“Funtion”一章中有所讨论。/default.aspx?scid=KB;EN-US;830555中也对这个 问题举了很详细的例子。一些 简单的解决方案 目前大多数ajax前端的javascript framework都利用对事件的管理,解决了该问题。如果你需要自己解决这个问题,可以参考以下的一些方法: /ieleak/index.php?title=Main_Page: 有个不错的检测工具 /2005/0221010713 中提到:可以利用递归Dom树,解除event绑定,从而解除循环引用: if (window.attachEvent) var clearElementProps = data, onmouseover, onmouseout, onmousedown, onmouseup, ondblclick, onclick, onselectstart, oncontextmenu ; window.attachEvent(onunload, function() var el; for(var d = document.all.length;d-;) el = document.alld; for(var c = clearElementProps.length;c-;) elclearElementPropsc = null; ); 而/javascript/event-cache一文中则通 过增加EventCache,从而给出一个相对结构化的解决方案/* EventCacheVersion1.0Copyright2005MarkWubbenProvidesawayforautomagicallyremovingeventsfromnodesandthuspreventingmemoryleakage.Seeformoreinformation.ThissoftwareislicensedundertheCC-GNULGPL*/ /* Implementarray.pushforbrowserswhichdontsupportitnatively.Pleaseremovethisifitsalreadyinothercode */ if (Atotype.push = null )Atotype.push = function () for ( var i = 0 ;i = 0 ;i = i - 1 )item = listEventsi; if (item 0 .removeEventListener)item 0 .removeEventListener(item 1 ,item 2 ,item 3 ); /* Fromthispointonweneedtheeventnamestobeprefixedwithon */ if (item 1 .substring( 0 , 2 ) != on )item 1 = on + item 1 ; if (item 0 .detachEvent)item 0 .detachEvent(item 1 ,item 2 );item 0 item 1 = null ;(); 使用方法也很简单:function addEvent(oEventTarget, sEventType, fDest) if(oEventTarget.attachEvent)oEventTarget.attachEvent(on + sEventType, fDest); elseif(oEventTarget.addEventListener)oEventTarget.addEventListener(sEventType, fDest, true); elseif(typeof oEventTargetsEventType = function) var fOld = oEventTargetsEventType;oEventTargetsEventType = function(e) fOld(e); fDest(e); ; else oEventTargetsEventType = fDest;/* Implementing EventCache for all event systems */EventCache.add(oEventTarget, sEventType, fDest, true);function createLeak() var body = document.body;function someHandler() return body;addEvent(body, click, someHandler);window.onload = function() var i = 500; while(i 0)createLeak();i = i - 1;window.onunload = EventCache.flush; /weblog/2005/03/js-memory-leaks.cfm 一文中的方法类似:/* *EventManager.js*byKeithGaughan*Thisallowseventhandlerstoberegisteredunobtrusively,andcleans*themuponunloadtopreventmemoryleaks.*Copyright(c)KeithGaughan,2005.*Allrightsreserved.Thisprogramandtheaccompanyingmaterials*aremadeavailableunderthetermsoftheCommonPublicLicensev1.0*(CPL)whichaccompaniesthisdistribution,andisavailableat*/licenses/cpl.php*ThissoftwareiscoveredbyamodifiedversionoftheCommonPublicLicense*(CPL),whereKeithGaughanistheAgreementSteward,andthelicensing*agreementiscoveredbythelawsoftheRepublicofIreland. */ / Forimplementationsthatdontincludethepush()methodsforarrays. if ( ! Atotype.push)Atotype.push = function (elem) this this .length = elem;var EventManager = _registry: null ,Initialise: function () if ( this ._registry = null ) this ._registry = ; / Registerthecleanuphandleronpageunload. EventManager.Add(window, unload , this .CleanUp);, /* *Registersaneventandhandlerwiththemanager.*paramobjObjecthandlerwillbeattachedto.*paramtypeNameofeventhandlerrespondsto.*paramfnHandlerfunction.*paramuseCaptureUseeventcapture.Falsebydefault.*Ifyoudontunderstandthis,ignoreit.*returnTrueifhandlerregistered,elsefalse. */ Add: function (obj,type,fn,useCapture) this .Initialise(); / Ifastringwaspassedin,itsanid. if ( typeof obj = string )obj = document.getElementById(obj); if (obj = null | fn = null ) return false ; / Mozilla/W3Clisteners? if (obj.addEventListener)obj.addEventListener(type,fn,useCapture); this ._registry.push(obj:obj,type:type,fn:fn,useCapture:useCapture); return true ; / IE-stylelisteners? if (obj.attachEvent & obj.attachEvent( on + type,fn) this ._registry.push(obj:obj,type:type,fn:fn,useCapture: false ); return true ; return false ;, /* *Cleansupalltheregisteredeventhandlers. */ CleanUp: function () for ( var i = 0 ;i EventManager._registry.length;i + ) with (EventManager._registryi) / Mozilla/W3Cliste

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论