网络编程
位置:首页>> 网络编程>> JavaScript>> 浏览器中的内存泄露(2)

浏览器中的内存泄露(2)

作者:winter 来源:winter 博客 发布时间:2008-05-03 16:53:00 

标签:内存,泄露,浏览器,js

2.内存泄露的原因

作为一门垃圾回收的语言(注意不是垃圾语言),内存泄露的原因只有一个:引擎的bug(本小节用于休闲、调节气氛)

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谈起,下面是微软的例子,


<html>
    <head>
        <script language="JScript">...
        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("<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()
        ...{
            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>
        <button onclick="LeakMemory()">Memory Leaking Insert</button>
        <button onclick="CleanMemory()">Clean Insert</button>
        <div id="hostElement"></div>
    </body>
</html>

看看结果吧,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。

to be continued......

coming soon:

  • 显式类型转换

  • 避免事件导致的循环引用

  • 不影响返回值地打破循环引用

  • 延迟appendChild

  • 代理DOM对象

接着阅读内存泄露详细解决办法

0
投稿

猜你喜欢

手机版 网络编程 asp之家 www.aspxhome.com