`
babydeed
  • 浏览: 235674 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

常规循环引用内存泄漏和Closure内存泄漏

    博客分类:
  • Ext
阅读更多

要了解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在http://blogs.msdn.com/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中已经被修复了http://www.quirksmode.org/blog/archives/2006/04/ie_7_and_javasc.html]。

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp 中有个示意图和简单的例子体现了这个问题:

<!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->< html >
    
< head >
        
< script language = " JScript " >

        
var  myGlobalObject;

        
function  SetupLeak()  // 产生循环引用,因此会造成内存泄露
        {
            
//  First set up the script scope to element reference
            myGlobalObject  =
                document.getElementById(
" LeakedDiv " );

            
//  Next set up the element to script scope reference
            document.getElementById( " LeakedDiv " ).expandoProperty  =
                myGlobalObject;
        }


        
function  BreakLeak()  // 解开循环引用,解决内存泄露问题
        {
            document.getElementById(
" LeakedDiv " ).expandoProperty  =
                
null ;
        }
        
</ script >
    
</ head >

    
< body onload = " SetupLeak() "  onunload = " BreakLeak() " >
        
< div id = " LeakedDiv " ></ div >
    
</ body >
</ html >
   上面这个例子,看似很简单就能够解决内存泄露的问题。可惜的是,当我们的代码中的结构复杂了以后,造成循环引用的原因开始变得多样,我们就没法那么容易观察到了,这时候,我们必须对代码进行仔细的检查。

尤其是当碰到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。

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp]有个例子极深刻地显示了该隐蔽性:

 

<!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->< html >
    
< head >
        
< script language = " JScript " >

        
function  AttachEvents(element)
        {
            
//  This structure causes element to ref ClickEventHandler  //element有个引用指向函数ClickEventHandler()
            element.attachEvent( " onclick " , ClickEventHandler);

            
function  ClickEventHandler()
            {
                
//  This closure refs element  //该函数有个引用指向AttachEvents(element)调用Scope,也就是执行了参数element。
                
            }
        }

        
function  SetupLeak()
        {
            
//  The leak happens all at once
            AttachEvents(document.getElementById( " LeakedDiv " ));
        }

        
</ script >
    
</ head >

    
< body onload = " SetupLeak() "  onunload = " BreakLeak() " >
        
< div id = " LeakedDiv " ></ div >
    
</ body >
</ html >

 

还有这个例子在IE 6中同样原因会引起泄露

<!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->

function  leakmaybe() {
var  elm  =  document.createElement( " DIV " );
  elm.onclick 
=   function () {
return   2   +   2 ;
  }
}

for  ( var  i  =   0 ; i   10000 ; i ++ ) {
  leakmaybe();
}

 

btw:
关于Closure的知识,大家可以看看http://jibbering.com/faq/faq_notes/closures.html这篇文章,习惯中文也可以看看zkjbeyond的blog,他对Closure这篇文章进行了简要的翻译:http://www.blogjava.net/zkjbeyond/archive/2006/05/19/47025.html。之所以会有这一系列的问题,关键就在于javascript是种函数式脚本解析语言,因此javascript中“函数中的变量的作用域是定义作用域,而不是动态作用域”,这点在犀牛书《JavaScript: The Definitive Guide》中的“Funtion”一章中有所讨论。
http://support.microsoft.com/default.aspx?scid=KB;EN-US;830555中也对这个问题举了很详细的例子。


一些 简单的解决方案

目前大多数ajax前端的javascript framework都利用对事件的管理,解决了该问题。

如果你需要自己解决这个问题,可以参考以下的一些方法:

  • http://youngpup.net/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.all[d]; for(var c = clearElementProps.length;c--;){ el[clearElementProps[c]] = null; } } }); }
  • http://novemberborn.net/javascript/event-cache一文中则通过增加EventCache,从而给出一个相对结构化的解决方案

    <!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->/*     EventCache Version 1.0
        Copyright 2005 Mark Wubben

        Provides a way for automagically removing events from nodes and thus preventing memory leakage.
        See <http://novemberborn.net/javascript/event-cache> for more information.
        
        This software is licensed under the CC-GNU LGPL <http://creativecommons.org/licenses/LGPL/2.1/>
    */

    /*     Implement array.push for browsers which don't support it natively.
        Please remove this if it's already in other code 
    */
    if (Array.prototype.push  ==   null ){
        Array.prototype.push 
    =   function (){
            
    for ( var  i  =   0 ; i  <  arguments.length; i ++ ){
                
    this [ this .length]  =  arguments[i];
            };
            
    return   this .length;
        };
    };

    /*     Event Cache uses an anonymous function to create a hidden scope chain.
        This is to prevent scoping issues. 
    */
    var  EventCache  =   function (){
        
    var  listEvents  =  [];
        
        
    return  {
            listEvents : listEvents,
        
            add : 
    function (node, sEventName, fHandler, bCapture){
                listEvents.push(arguments);
            },
        
            flush : 
    function (){
                
    var  i, item;
                
    for (i  =  listEvents.length  -   1 ; i  >=   0 ; i  =  i  -   1 ){
                    item 
    =  listEvents[i];
                    
                    
    if (item[ 0 ].removeEventListener){
                        item[
    0 ].removeEventListener(item[ 1 ], item[ 2 ], item[ 3 ]);
                    };
                    
                    
    /*  From this point on we need the event names to be prefixed with 'on"  */
                    
    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 ;
                };
            }
        };
    }();
  • 使用方法也很简单:

    												
    <script type="text/javascript">
    function addEvent(oEventTarget, sEventType, fDest){
            if(oEventTarget.attachEvent){
    		oEventTarget.attachEvent("on" + sEventType, fDest);
    	} elseif(oEventTarget.addEventListener){
    		oEventTarget.addEventListener(sEventType, fDest, true); 
    	} elseif(typeof oEventTarget[sEventType] == "function"){
                    var fOld = oEventTarget[sEventType];
    		oEventTarget[sEventType] = function(e){ fOld(e); fDest(e); };
    	} else {
    		oEventTarget[sEventType] = 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;
    </script>
  • http://talideon.com/weblog/2005/03/js-memory-leaks.cfm 一文中的方法类似:

    <!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->/*
     * EventManager.js
     * by Keith Gaughan
     *
     * This allows event handlers to be registered unobtrusively, and cleans
     * them up on unload to prevent memory leaks.
     *
     * Copyright (c) Keith Gaughan, 2005.
     *
     * All rights reserved. This program and the accompanying materials
     * are made available under the terms of the Common Public License v1.0
     * (CPL) which accompanies this distribution, and is available at
     * http://www.opensource.org/licenses/cpl.php
     *
     * This software is covered by a modified version of the Common Public License
     * (CPL), where Keith Gaughan is the Agreement Steward, and the licensing
     * agreement is covered by the laws of the Republic of Ireland.
     
    */

    //  For implementations that don't include the push() methods for arrays.
    if  ( ! Array.prototype.push) {
        Array.prototype.push 
    =   function (elem) {
            
    this [ this .length]  =  elem;
        }
    }

    var  EventManager  =  {
        _registry: 
    null ,

        Initialise: 
    function () {
            
    if  ( this ._registry  ==   null ) {
                
    this ._registry  =  [];

                
    //  Register the cleanup handler on page unload.
                EventManager.Add(window,  " unload " this .CleanUp);
            }
        },

        
    /* *
         * Registers an event and handler with the manager.
         *
         * @param  obj         Object handler will be attached to.
         * @param  type        Name of event handler responds to.
         * @param  fn          Handler function.
         * @param  useCapture  Use event capture. False by default.
         *                     If you don't understand this, ignore it.
         *
         * @return True if handler registered, else false.
         
    */
        Add: 
    function (obj, type, fn, useCapture) {
            
    this .Initialise();

            
    //  If a string was passed in, it's an id.
             if  ( typeof  obj  ==   " string " ) {
                obj 
    =  document.getElementById(obj);
            }
            
    if  (obj  ==   null   ||  fn  ==   null ) {
                
    return   false ;
            }

            
    //  Mozilla/W3C listeners?
             if  (obj.addEventListener) {
                obj.addEventListener(type, fn, useCapture);
                
    this ._registry.push({obj: obj, type: type, fn: fn, useCapture: useCapture});
                
    return   true ;
            }

            
    //  IE-style listeners?
             if  (obj.attachEvent  &&  obj.attachEvent( " on "   +  type, fn)) {
                
    this ._registry.push({obj: obj, type: type, fn: fn, useCapture:  false });
                
    return   true ;
            }

            
    return   false ;
        },

        
    /* *
         * Cleans up all the registered event handlers.
         
    */
        CleanUp: 
    function () {
            
    for  ( var  i  =   0 ; i  <  EventManager._registry.length; i ++ ) {
                
    with  (EventManager._registry[i]) {
                    
    //  Mozilla/W3C listeners?
                     if  (obj.removeEventListener) {
                        obj.removeEventListener(type, fn, useCapture);
                    }
                    
    //  IE-style listeners?
                     else   if  (obj.detachEvent) {
                        obj.detachEvent(
    " on "   +  type, fn);
                    }
                }
            }

            
    //  Kill off the registry itself to get rid of the last remaining
             //  references.
            EventManager._registry  =   null ;
        }
    };

    使用起来也很简单

    												
    <html>
    <head>
    <script type=text/javascript src=EventManager.js></script>
    <script type=text/javascript>
        function onLoad() {
    
        EventManager.Add(document.getElementById(testCase),click,hit );
    returntrue;
        }
    
        function hit(evt) {
            alert(click);
        }
    </script>
    </head>
    
    <body onload='javascript: onLoad();'>
    
    <div id='testCase' style='width:100%; height: 100%; background-color: yellow;'>
      <h1>Click me!</h1>
    </div>
    
    </body>
    </html>
  • google map api同样提供了一个类似的函数用在页面的unload事件中,解决Closure带来的内存泄露问题。
  • 当然,如果你不嫌麻烦,你也可以为每个和native object有关的就阿vascript object编写一个destoryMemory函数,用来手动调用,从而手动解除Dom对象的事件绑定。

Cross-Page Leaks

    Cross-Page Leaks和下一节提到的Pseudo-Leaks在我看来,就是IE的bug, 虽然MS死皮赖脸不承认:)

     大家可以看看这段例子代码:

 

<!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->< html >
    
< head >
        
< script language = " JScript " >

        
function  LeakMemory()  // 这个函数会引发Cross-Page Leaks
        {
            
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(
" <div onClick='foo()'> " );
                
var  childDiv  =
                    document.createElement(
" <div onClick='foo()'> " );

                
//  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()  // 而这个函数不会引发Cross-Page Leaks
        {
            
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( " <div onClick='foo()'> " );
                
var  childDiv  =   document.createElement( " <div onClick='foo()'> " );

                
//  Changing the order is important, this won't leak
                hostElement.appendChild(parentDiv);
                parentDiv.appendChild(childDiv);
                hostElement.removeChild(parentDiv);
                parentDiv.removeChild(childDiv);
                parentDiv 
=   null ;
                childDiv 
=   null ;
            }
            hostElement 
=   null ;
        }
        
</ script >
    
</ head >

    
< body

转自:http://04js.cn/content.asp?id=1612

分享到:
评论

相关推荐

    closure闭包

    求文法的closure闭包,针对每个产生式,求其closure闭包,并打印输出

    Closure The Definitive Guide

    If you're ready to use Closure to build rich web applications with JavaScript, this hands-on guide has precisely what you need to learn this suite of tools in depth. Closure makes it easy for ...

    Closure tools

    Closure toolsClosure toolsClosure toolsClosure tools

    Google Javascript Closure Compiler

    closure-compiler-v20170521.jar,以及一个.chm使用说明:‘Getting Started with the Closure Compiler Application’,‘Advanced Compilation and Externs’,‘Understanding the Restrictions Imposed by the ...

    Closure-Table-ClosureTable.rar

    主要完成了数据库存储概念中Closure-Table 存储,写了一个SSM简单的项目 项目是github上的,github也是我自己的项目

    Closure Linter完整安装包

    Closure Linter完整安装包,包含安装说明。 python-2.7.3.msi setuptools-0.6c11.win32-py2.7.exe

    Javascript Closure

    Closure: the definitive guide, by Michael Bolin

    Closure Compiler exe

    Closure Compiler exe 根据[在项目中使用Google Closure Compiler](http://www.cnblogs.com/JeffreyZhao/archive/2009/12/09/ikvm-google-closure-compiler.html)

    Closure Templates

    套工具就是提供一个在JavaScript中做模板的机制及函数库,你可以把HTML布局的部份写成一个*.soy,接着再利用...Closure Templates既可以实现JavaScript,又可以实现Java,所以你可以在服务器端和客户端使用相同的模板。

    Closure: The Definitive Guide

    Closure_ The Definitive Guide js的闭包

    Closure闭包函数示例

    补充说明:闭包可以使用USE关键连接外部变量。 总结:PHP闭包的特性其实用CLASS就可以实现类似甚至强大得多的功能,更不能和js的闭包相提并论了吧,只能期待PHP以后对闭包支持...合理使用闭包能使代码更加简洁和精炼。

    Closure编译器的纯JavaScript版本最高级的构建工具

    Closure编译器的纯JavaScript版本,最高级的构建工具

    loop_closure_闭环检测_loopclosure_

    实现机器人运动的闭环检测,是为同时与建图增加约束条件,提高精度

    closure dependency not found解决包

    当我们从github上下载了blockly之后,打卡demos下的index.html时,选择blockly-developer-tools时会弹出一个对话框(大体内容是closure dependency not found),此时...放在和blockly同目录下,然后重新选择就好了。

    使用Google Closure Compiler js压缩

    使用Google Closure Compiler js压缩

    Laravel开发-closure-table

    Laravel开发-closure-table Laravel的邻接表闭包表数据库设计模式实现

    前端开源库-closure-loader

    前端开源库-closure-loader闭包加载器,用于Google闭包库依赖项的Webpack加载器

    closure-webpack-plugin:Webpack Google Closure编译器和Closure库插件-

    关闭webpack插件 此插件支持将Google的关闭工具与webpack一起使用。...您必须同时安装google-closure-compiler软件包和closure-webpack-plugin。 npm install --save-dev closure-webpack-plugin

    论文《Real-time loop closure in 2D LIDAR SLAM》

    google cartographer算法对应论文real-time loop closure in 2D LIDAR SLAM

    Closure Compiler

    Closure Compiler是用来编译JavaScript的编译器,除了像最常见的JavaScript的压缩机提供的功能,它还会对程序进行分析,把不需要的部份移除,减少的JavaScript程序的大小及提升效率。

Global site tag (gtag.js) - Google Analytics