`

[总结转载]JavaScript 内存泄露

阅读更多

JavaScript 内存泄露

今天下午同事让帮忙看web内存泄露问题。当时定位到创建ActiveX 对象的时候产生的,于是我对这个奇怪的问题进行了一些深入探索。 

很多时候我都依赖javascript的垃圾回收机制,所以对C 以及C++ 操作内存语言常发生的内存泄露是很陌生的。当时创建回调函数用了闭包,当然最终的解决方法是也避免闭包调用。 

    

    随着这个问题的浮出水面,我回忆起以前的一个项目中也应该存在这个内存泄露问题。于是查阅了相关资料把类似的问题总结下来,希望对大家也有帮助。

    原因:对于一门具有垃圾收回机制的语言存在内存泄露,其原因不外乎就是javascript脚本引擎存在bug。

   很多时候,我们要做的不是去修正那样的bug,而是想办法去规避。

目前发现的可能导致内存泄露的代码有三种:

· 循环引用

· 自动类型装箱转换

· 某些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方法

我曾经看到过这样的说法,创建dom的时候,先创建子节点,当子节点完善后一次性添加到页面中,不要一点点朝页面上加东西,尽量减少document刷新次数,这样效率会高点。(打个比方就是应该像 LeakMemory )可见这里我还是被某些书籍误导了。至少他没有告诉我内存泄露的问题。

接下来是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对象

· 显式类型转换

首先说说最容易处理的情况 对于类型转换造成的错误,我们可以通过显式类型转换来避免:

var s=newString("lalalalala");//此处将string转换成object
alert(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;i<this.parts.length;i++)
        parts.appendTo(this);

 

· 垃圾箱

对于ie7,我比较无可奈何,因为DOM对象不会被CG程序回收,只有离开页面时会被回收,所以我的建议是:使用DOM要有节制,尽量多用innerHTML吧...... good luck.

一旦你使用了DOM对象,千万不要试图o=null,你可以设置一个叫做Garbage的div并且将其display设置为none,将不用的DOM对象存入其中(就是appendChild上去)就好了font-size: 11pt; background: rgb(255,255,255); color: rgb(0,0,

分享到:
评论

相关推荐

    Javascript内存泄露

    ### JavaScript内存泄露详解 #### 一、什么是JavaScript内存泄露? 在JavaScript编程中,内存泄露指的是在浏览器中不再使用的变量或对象占用的内存没有被及时回收,导致可用内存逐渐减少的现象。这种现象通常发生...

    【JavaScript源代码】一篇文章弄懂javascript内存泄漏.docx

    【JavaScript源代码】一篇文章弄懂javascript内存泄漏 在JavaScript中,内存管理对于程序性能至关重要,因为内存泄漏会导致程序效率下降,甚至可能导致应用崩溃。本文旨在深入解析JavaScript中的内存泄漏及其解决...

    测试JavaScript在IE中的内存泄露

    2. **内存泄漏类型**:常见的JavaScript内存泄露包括全局变量、闭包引用、DOM元素引用、事件监听器等。了解这些类型有助于识别潜在问题。 3. **工具使用**:利用如IE Developer Tools(F12工具)、Chrome DevTools...

    JavaScript中的内存泄漏检测方法研究.pdf

    JavaScript中的内存泄漏检测方法研究 在 JavaScript 中,内存泄漏是一种常见的问题,它会导致应用程序的性能下降和崩溃。检测内存泄漏是一个复杂的任务,需要对内存泄漏的原因和机理进行深入分析。本文研究了十一种...

    javascript课程内容总结

    JavaScript 课程内容总结 JavaScript 是一种广泛应用于网页开发的编程语言,以下是 JavaScript 的基础知识点总结。 数据类型 在 JavaScript 中,数据类型包括字符串(string)、数值型(number)、布尔型...

    electron-vue开发环境内存泄漏问题汇总

    在使用Electron结合Vue进行桌面应用开发时,内存泄漏是一个常见且需要重点关注的问题。内存泄漏问题的出现会逐渐消耗系统资源,最终可能导致应用崩溃或运行缓慢。在Electron中,内存泄漏主要跟主进程和渲染进程的...

    jquery 内存泄露bug

    标题 "jquery 内存泄露bug" 指的是在使用jQuery库进行JavaScript编程时可能出现的一种常见问题,即内存无法正常释放,导致应用占用过多内存,影响性能甚至可能导致浏览器崩溃。这个问题通常与对象引用、事件监听器和...

    详谈JavaScript内存泄漏

    最后,我们应当总结,JavaScript内存泄漏是一个需要高度重视的问题。通过深入理解闭包的工作原理和JavaScript的垃圾回收机制,我们可以编写出更加安全和高效的代码,从而避免不必要的内存泄漏。特别是在处理IE等特定...

    【JavaScript源代码】vue内存泄露详解.docx

    对于JavaScript来说,由于其采用自动垃圾回收机制,内存泄露通常指的是那些无法通过常规手段被自动回收的内存。 内存泄露的危害主要体现在以下几个方面: - **性能下降**:内存泄露会导致可用内存减少,使得应用...

    ajax js性能优化和内存泄露检测工具

    本文将深入探讨AJAX和JavaScript在性能优化方面的策略,以及如何使用内存泄露检测工具来确保高效且无泄漏的代码。 一、AJAX性能优化 1. **减少HTTP请求**:每个HTTP请求都会带来一定的开销,包括建立连接、发送...

    本文主要介绍了JavaScript几种常见的内存泄露

    然而,由于其特定的运行环境和语法特性,JavaScript中依然存在一些可能导致内存泄露的情况。本文将深入探讨JavaScript中的几种常见内存泄露模式,并提供相应的解决方案。 1. **全局变量和闭包引用** 全局变量生命...

    javascript 数组内存释放

    本篇文章将深入探讨JavaScript数组的内存释放机制,以及如何有效地管理数组内存。 首先,理解JavaScript的内存管理机制至关重要。JavaScript使用了一种称为垃圾回收(Garbage Collection, GC)的自动内存管理系统,...

    javascript 内存模型实例详解

    JavaScript的内存模型是理解其运行机制的关键部分,...理解JavaScript内存模型有助于优化代码性能,避免内存泄漏,并有效地管理变量。通过深入学习这些基础知识,开发者可以更好地控制程序的运行,提高代码质量和效率。

    Drip 检测IE内存泄漏

    内存泄漏是编程中的一个常见问题,尤其是在JavaScript环境中,由于IE浏览器的内存管理机制,这个问题显得尤为突出。Drip作为一个实用的工具,帮助开发者定位和解决这一问题,提高Web应用程序的性能和稳定性。 在...

    内存javascript脚本

    对于JavaScript来说,内存泄漏通常是由于未被正确清除的事件监听器、闭包中的引用未能及时释放等原因造成的。以下是一些解决内存泄漏的方法: 1. **移除事件监听器**:确保在不再需要时移除事件监听器。 2. **合理...

    V8堆快照的内存自动泄漏检测

    V8堆快照的内存自动泄漏检测是JavaScript开发中的一项重要技术,主要目的是发现和解决JavaScript应用程序中的内存泄漏问题。内存泄漏会导致程序占用过多的内存,影响性能甚至导致程序崩溃。Eclipse Memory Analyzer...

    JavaScript闭包技术及IE内存泄漏分析.pdf

    JavaScript闭包技术及IE内存泄漏分析

    容易造成JavaScript内存泄露几个方面

    在JavaScript编程中,内存泄漏是常见的问题,它不仅会导致应用程序运行缓慢,还有可能引发页面崩溃。以下将详细解释几种易导致JavaScript内存泄漏的情况,并涉及如何使用Chrome DevTools进行内存管理与调试。 1. ...

    【温故而知新】JavaScript中内存泄露有那几种.md

    【温故而知新】JavaScript中内存泄露有那几种

Global site tag (gtag.js) - Google Analytics