应用程序中的内部存储器泄漏

转贴:http://www.ibm.com/developerworks/cn/web/wa-jsmemory/

www.4355mg娱乐游戏,检查和测试和化解内部存储器难题

Ben
Dolmar
,
软件开发人士, The Nerdery

 

简介: 垃圾回收解放了大家,它让大家可将精力集中在应用程序逻辑(而不是内部存储器管理)上。可是,垃圾收集并不神奇。领悟它的做事原理,以及如何使它保留本应在很久此前释放的内部存储器,就能够兑现更快更可相信的应用程序。在本文中,学习一种固定
JavaScript
应用程序中内部存款和储蓄器泄漏的系统方法、二种常见的败露形式,以及消除那么些泄漏的适龄措施。

简介

当处理 JavaScript
那样的脚本语言时,很不难忘记每种对象、类、字符串、数字和办法都亟待分配和封存内部存款和储蓄器。语言和平运动转时的废品回收器隐藏了内部存款和储蓄器分配和自由的求实细节。

成都百货上千成效无需考虑内部存款和储蓄器管理即可实现,但却忽略了它大概在先后中带动重庆大学的难题。不当清理的对象恐怕会存在比预料要长得多的光阴。这一个目的继续响应事件和消耗财富。它们可强制浏览器从二个虚拟磁盘驱动器分配内部存款和储蓄器页,那明显影响了电脑的快慢(在最为的状态中,会造成浏览器崩溃)。

内部存款和储蓄器泄漏指任何对象在你不再持有或需求它之后依然存在。在近日几年中,许多浏览器都句斟字酌了在页面加载进度中从
JavaScript
回收内部存款和储蓄器的力量。可是,并不是负有浏览器都持有同样的运作格局。Firefox
和旧版的 Internet Explorer
都设有过内部存款和储蓄器泄漏,而且内部存款和储蓄器走漏一向持续到浏览器关闭。

千古导致内部存储器泄漏的好多经典情势在当代浏览器中以不再导致泄漏内部存款和储蓄器。然则,近来有一种不一致的动向影响着内存泄漏。许多少人正安插用来在平昔不硬页面刷新的单页中运营的
Web
应用程序。在那么的单页中,从应用程序的一个场所到另3个动静时,很简单保留不再必要或不相干的内部存款和储蓄器。

在本文中,精通对象的中坚生命周期,垃圾回收如何规定3个对象是否被保释,以及哪些评估潜在的泄漏行为。别的,学习怎么着利用
谷歌(Google) Chrome 中的 Heap Profiler
来诊断内部存款和储蓄器难题。一些演示显示了怎么缓解闭包、控制台日志和循环带来的内存泄漏。

您可 下载 本文中选择的示范的源代码。

 

回页首

目的生命周期

要打听什么幸免内部存款和储蓄器泄漏,需求领会对象的主导生命周期。当成立一个目的时,JavaScript
会自动为该指标分配适当的内部存款和储蓄器。从这一阵子起,垃圾回收器就会不停对该指标开始展览评估,以查看它是或不是仍是卓有作用的靶子。

垃圾堆回收器定期扫描对象,并总计引用了种种对象的别样对象的数码。若是3个指标的引用数量为
0(没有别的对象引用过该指标),或对该指标的绝世引用是循环的,那么该对象的内部存款和储蓄器即可回收。图
1 体现了垃圾回收器回收内部存款和储蓄器的1个演示。

图 1. 通过垃圾收集回收内部存款和储蓄器
www.4355mg娱乐游戏 1 

观望该体系的实际上选择会很有帮带,但提供此功用的工具很单薄。理解您的
JavaScript
应用程序占用了不怎么内部存款和储蓄器的一种方法是选择系统工具查看浏览器的内存分配。有七个工具可为您提供当前的行使,并形容3个经过的内部存储器使用量随时间变化的可行性图。

譬如,即使在 Mac OSX 上设置了 XCode,您可以运营 Instruments
应用程序,并将它的移位监视器工具附加到您的浏览器上,以拓展实时分析。在
Windows®
上,您能够使用职务管理器。假若在您使用应用程序的经过中,发现内部存款和储蓄器使用量随时间变化的曲线稳步上升,那么你就知道存在内存泄漏。

观测浏览器的内部存款和储蓄器占用只可以相当粗略地呈现 JavaScript
应用程序的骨子里内部存款和储蓄器使用。浏览器数据不会告诉您哪些对象发生了泄漏,也无从有限支撑数据与你应用程序的实在内部存储器占用确实匹配。而且,由于一些浏览器中设有贯彻难点,DOM
成分(或备用的选用程序级对象)恐怕不会在页面中销毁相应成分时释放。录像标记尤为如此,录像标记须要浏览器完结一种特别小巧的基础架构。

芸芸众生曾多次尝试在客户端 JavaScript
库中拉长对内部存款和储蓄器分配的跟踪。不幸的是,全数尝试都不是尤其可相信。例如,流行的
stats.js
包由于禁止确性而无法支撑。一般而言,尝试从客户端维护或规定此音信存在必然的难点,是因为它会在应用程序中引入开销且不可能可信赖地截至。

精美的缓解方案是浏览器供应商在浏览器中提供一组织工作具,协助您监视内部存款和储蓄器使用,识别泄漏的指标,以及显著为何3个奇特对象仍标记为保留。

眼下,只有 谷歌(Google) Chrome(提供了 Heap
Profile)达成了3个内部存款和储蓄器管理工具作为它的开发人士工具。作者在本文中央银行使 Heap
Profiler 测试和演示 JavaScript 运转时怎么处理内部存款和储蓄器。

 

回页首

分析堆快速照相

在创设内部存款和储蓄器泄漏从前,请查看二次适当收集内部存款和储蓄器的大概交互。首先创制二个含有多少个按钮的简练
HTML 页面,如清单 1 所示。

清单 1. index.html

               
<html>
<head>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" 
type="text/javascript"></script>
</head>
<body>
    <button id="start_button">Start</button>
    <button id="destroy_button">Destroy</button>
    <script src="assets/scripts/leaker.js" type="text/javascript" 
charset="utf-8"></script>
    <script src="assets/scripts/main.js" type="text/javascript" 
charset="utf-8"></script>
</body>
</html>

 

带有 jQuery
是为着保障一种管总管件绑定的简便语法适合分化的浏览器,而且严苛遵从最广泛的支付执行。为 leaker 类和第2JavaScript 方法添加脚本标记。在开发条件中,将 JavaScript
文件合并到单个文件中不足为奇是一种更好的做法。出于本示例的用途,将逻辑放在独立的文件中更易于。

你能够过滤 Heap Profiler
来仅浮现特殊类的实例。为了选取该成效,创制多个新类来封装泄漏对象的表现,而且以此类很不难在
Heap Profiler 中找到,如清单 2 所示。

清单 2. assets/scripts/leaker.js

               
var Leaker = function(){};
Leaker.prototype = {
    init:function(){

    }    
};

 

绑定 Start
按钮以起初化 Leaker 对象,并将它分配给全局命名空间中的二个变量。还亟需将
Destroy
按钮绑定到二个应清理 Leaker对象的点子,并让它为垃圾收集做好准备,如清单
3 所示。

清单 3. assets/scripts/main.js

               
$("#start_button").click(function(){
    if(leak !== null || leak !== undefined){
        return;
    }
  leak = new Leaker();
  leak.init();
});

$("#destroy_button").click(function(){
    leak = null;
});

var leak = new Leaker();

 

于今,您已准备好成立三个指标,在内部存储器中查看它,然后释放它。

  1. 在 Chrome 中加载索引页面。

    因为你是直接从 谷歌 加载 jQuery,所以供给一连网络来运营该样例。

  2. 开拓开发人职员和工人具,方法是开拓 View 菜单并选用 Develop
    子菜单。接纳 Developer Tools 命令。

  3. 转到 Profiles 选项卡并拿走二个堆快速照相,如图 2 所示。
**图 2. Profiles 选项卡**  
![](http://www.ibm.com/developerworks/cn/web/wa-jsmemory/fig02.jpg)   
  1. 将注意力重临到 Web 上,选拔 Start
  2. 取得另二个堆快速照相。
  3. 过滤第四个快速照相,查找 Leaker 类的实例,找不到其余实例。切换来首个快速照相,您应该能找到三个实例,如图
    3 所示。
**图 3. 快照实例**  
![](http://www.ibm.com/developerworks/cn/web/wa-jsmemory/fig03.jpg)   
  1. 将注意力再次来到到 Web 上,接纳 Destroy
  2. 收获第多少个堆快速照相。
  3. 过滤第五个快照,查找 Leaker 类的实例,找不到别的实例。

    在加载第一个快速照相时,也可将分析形式从 Summary 切换到Comparison,并对照第五个和第二个快速照相。您会看出偏移值
    -1(在一回快速照相之间自由了 Leaker 对象的贰个实例)。

大王!垃圾回收有效的。未来是时候破坏它了。

 

回页首

内部存款和储蓄器泄漏 1:闭包

一种预防一个对象被垃圾回收的简练方法是设置一个在回调中引用该对象的距离或过期。要查看实际应用,可更新
leaker.js 类,如清单 4 所示。

清单 4. assets/scripts/leaker.js

               
var Leaker = function(){};

Leaker.prototype = {
    init:function(){
        this._interval = null;
        this.start();
    },

    start: function(){
        var self = this;
        this._interval = setInterval(function(){
            self.onInterval();
        }, 100);
    },

    destroy: function(){
        if(this._interval !== null){
            clearInterval(this._interval);          
        }
    },

    onInterval: function(){
        console.log("Interval");
    }
};

 

现在,当重复 上一节 中的第二-9
步时,您应在第多少个快速照相中看看,Leaker 对象被持久化,并且该间隔会永远持续运转。那么发生了怎么?在二个闭包中援引的任何部分变量都会被该闭包保留,只要该闭包存在就永远保存。要保障对 setInterval 方法的回调在拜访
Leaker
实例的限量时举行,须要将 this 变量分配给部分变量 self,这些变量用于从闭包内触发 onInterval。当 onInterval 触发时,它就能够访问 Leaker 对象中的任何实例变量(包涵它本人)。不过,只要事件侦听器存在,Leaker 对象就不会被垃圾回收。

要解决此难点,可在清空所蕴藏的 leaker 对象引用在此之前,触发添加到该对象的 destroy 方法,方法是翻新
Destroy 按钮的单击处理程序,如清单 5 所示。

清单 5. assets/scripts/main.js

               
$("#destroy_button").click(function(){
    leak.destroy();
    leak = null;
});

 

 

回页首

销毁对象和目的全部权

一种科学的做法是,创制四个正规方法来顶住让一个对象有资格被垃圾回收。destroy
功用的首要用途是,集中清理该对象落成的装有以下后果的操作的义务:

  • 截留它的引用计数下落到
    0(例如,删除存在难题的风浪侦听器和回调,并从别的劳动撤消注册)。
  • 选择不供给的 CPU 周期,比如间隔或动画。

destroy 方法日常是理清一个目的的必需步骤,但在大部分意况下它还不够。在理论上,在销毁相关实例后,保留对已灭相对象的引用的此外对象可调用自己之上的法门。因为那种情状可能会产生不可预测的结果,所以仅在目的即将无用时调用
destroy 方法,那第3。

相似而言,destroy
方法最好使用是在一个目的有1个斐然的持有者来顶住它的生命周期时。此意况常常存在于分层系统中,比如
MVC 框架中的视图或控制器,大概一个画布展现系统的场景图。

 

回页首

内部存款和储蓄器泄漏 2:控制台日志

一种将对象保留在内部存储器中的不太显眼的法子是将它记录到控制罗利。清单 6
更新了 Leaker 类,显示了此格局的1个演示。

清单 6. assets/scripts/leaker.js

               
var Leaker = function(){};

Leaker.prototype = {
    init:function(){
        console.log("Leaking an object: %o", this);
    },

    destroy: function(){

    }      
};

 

可选择以下步骤来演示控制台的影响。

  1. 登录到目录页面。
  2. 单击 Start
  3. 转到控制台并认同 Leaking 对象已被跟踪。
  4. 单击 Destroy
  5. 归来控制台并键入 leak,以记录全局变量当前的始末。此刻该值应为空。
  6. 收获另一个堆快照并过滤 Leaker 对象。

    您应预留3个 Leaker 对象。

  7. 再次来到控制台并消除它。

  8. 创制另四个堆配置文件。

    在清理控制台后,保留 leaker 的安插文件应已消除。

控制台日志记录对完全内部存款和储蓄器配置文件的影响大概是诸多开发人士都未想到的无比关键的难题。记录错误的靶子能够将大气数额保存在内部存款和储蓄器中。注意,那也适用于:

  • 在用户键入 JavaScript 时,在控制斯特拉斯堡的3个交互式会话时期记录的靶子。
  • 由 console.log 和 console.dir 方法记录的指标。

 

回页首

内部存款和储蓄器泄漏 3:循环

在八个对象相互引用且相互保留时,就会时有爆发二个循环,如图 4 所示。

图 4. 创办三个循环的引用
www.4355mg娱乐游戏 2 

清单 7 彰显了三个粗略的代码示例。

清单 7. assets/scripts/leaker.js

               
var Leaker = function(){};

Leaker.prototype = {
    init:function(name, parent){
        this._name = name;
        this._parent = parent;
        this._child = null;
        this.createChildren();
    },

    createChildren:function(){
        if(this._parent !== null){
            // Only create a child if this is the root
            return;
        }
        this._child = new Leaker();
        this._child.init("leaker 2", this);
    },

    destroy: function(){

    }
};

 

Root 对象的实例化能够修改,如清单 8 所示。

清单 8. assets/scripts/main.js

               
leak = new Leaker(); 
leak.init("leaker 1", null);

 

借使在开创和销毁对象后执行3遍堆分析,您应该会看到垃圾收集器检查和测试到了那个轮回引用,并在你选拔Destroy 按钮时释放了内存。

而是,如若引入了第一个保留该子对象的靶子,该循环会导致内部存款和储蓄器泄漏。例如,创造八个 registry 对象,如清单
9 所示。

清单 9. assets/scripts/registry.js

               
var Registry = function(){};

Registry.prototype = {
    init:function(){
        this._subscribers = [];
    },

    add:function(subscriber){
        if(this._subscribers.indexOf(subscriber) >= 0){
            // Already registered so bail out
            return;
        }
        this._subscribers.push(subscriber);
    },

    remove:function(subscriber){
        if(this._subscribers.indexOf(subscriber) < 0){
            // Not currently registered so bail out
            return;
        }
              this._subscribers.splice(
                  this._subscribers.indexOf(subscriber), 1
              );
    }
};

 

registry 类是让别的对象向它注册,然后从注册表中删去自个儿的指标的粗略示例。固然这几个新鲜的类与注册表毫非亲非故联,但那是事件调度程序和文告系统中的一种常见形式。

将此类导入 index.html 页面中,放在 leaker.js 在此之前,如清单 10 所示。

清单 10. index.html

               
<script src="assets/scripts/registry.js" type="text/javascript" 
charset="utf-8"></script>

 

更新 Leaker 对象,以向注册表对象注册该对象自小编(恐怕用于有关部分未落实事件的布告)。那创立了2个源点要保存的
leaker 子对象的 root 节点备用路径,但鉴于该循环,父对象也将保存,如清单
11 所示。

清单 11. assets/scripts/leaker.js

               
var Leaker = function(){};
Leaker.prototype = {

    init:function(name, parent, registry){
        this._name = name;
        this._registry = registry;
        this._parent = parent;
        this._child = null;
        this.createChildren();
        this.registerCallback();
    },

    createChildren:function(){
        if(this._parent !== null){
            // Only create child if this is the root
            return;
        }
        this._child = new Leaker();
        this._child.init("leaker 2", this, this._registry);
    },

    registerCallback:function(){
        this._registry.add(this);
    },

    destroy: function(){
        this._registry.remove(this);
    }
};

 

最后,更新 main.js
以设置注册表,并将对注册表的多少个引用传递给 leaker 父对象,如清单 12
所示。

清单 12. assets/scripts/main.js

               
      $("#start_button").click(function(){
  var leakExists = !(
          window["leak"] === null || window["leak"] === undefined
      );
  if(leakExists){
      return;
  }
  leak = new Leaker();
  leak.init("leaker 1", null, registry);
});

$("#destroy_button").click(function(){
    leak.destroy();
    leak = null;
});

registry = new Registry();
registry.init();

 

未来,当执行堆分析时,您应看来每回接纳 Start
按钮时,会创立并保留 Leaker 对象的多少个新实例。图 5
彰显了指标引用的流水生产线。

图 5. 出于保留引用导致的内部存款和储蓄器泄漏
www.4355mg娱乐游戏 3 

从表面上看,它像3个不自然的以身作则,但它实际上卓殊普遍。尤其经典的面向对象框架中的事件侦听器平常遵从类似图
5 的方式。那体系型的格局也大概与闭包和控制台日志导致的标题相关联。

尽管有三种艺术来缓解此类难点,但在此处境下,最简便易行的章程是更新 Leaker 类,以在销毁它时销毁它的子对象。对于本示例,更新 destroy 方法(如清单
13 所示)就丰裕了。

清单 13. assets/scripts/leaker.js

               
destroy: function(){
    if(this._child !== null){
        this._child.destroy();            
    }
    this._registry.remove(this);
}

 

有时候,几个从未丰硕紧凑关系的靶子之间也会存在循环,个中多少个对象管理另3个目的的生命周期。在这么的图景下,在那三个对象时期确立关联的靶子应负责在友好被销毁时停顿循环。

 

回页首

结束语

即使 JavaScript
已被垃圾回收,仍旧会有不少艺术会将不供给的目的保留在内部存款和储蓄器中。近期超越一半浏览器都已革新了内部存储器清理功效,但评估您应用程序内部存款和储蓄器堆的工具仍旧有限(除了利用
谷歌(Google)Chrome)。通过从简单的测试案例伊始,很不难评估潜在的走漏行为并分明是不是留存败露。

不通过测试,就不容许准确衡量内部存款和储蓄器使用。很不难使循环引用占据对象曲线图中的大部分区域。Chrome
的 Heap Profiler
是几个确诊内部存款和储蓄器难点的可贵工具,在付出时定期采纳它也是二个不利的取舍。在估量指标曲线图中要自由的现实性财富时请设定具体的预期,然后举行认证。任哪天候当您看看不想要的结果时,请仔细调查。

在创立对象时要布署该对象的清理工科作,这比在事后将一个清理阶段移植到应用程序中要不难得多。平日要陈设删除事件侦听器,并终止您创建的间隔。要是认识到了你应用程序中的内部存款和储蓄器使用,您将收获更保障且质量更高的应用程序。

 

 

回页首

下载

描述 名字 大小 下载方法
文章源代码 JavascriptMemoryManagementSource.zip 4KB HTTP

至于下载方式的新闻

 

参考资料

学习

获取产品和技巧

讨论

  • developerWorks
    社区
    :查看开发职员拉动的博客、论坛、群组和维基,并与其余developerWorks 用户交流。

关于小编

www.4355mg娱乐游戏 4

Ben Dolmar 自 二〇〇三 年起就起来了标准编制程序,拥有丰盛的技艺经验,包蕴ActionScript、iOS、JavaScript、PHP、Ruby 和图片设计。自 1996年从内布拉斯加大学Madison分校完成学业并拿走音讯和政治学双学位后,Ben 在 1997年到 二〇〇五 年时期担任 Faith Inkubators 的成品主任。自 二零零六 年加盟 The
Nerdery 以来,他已帮带运行了 400 五个门类。在 2000 年,Ben
被唤醒为首席软件工程师。他曾在 SWFCamp 举行过 ActionScript
方面包车型大巴解说,到场过在圣Paul进行的关于不断衍变的 Web 标准和 HTML 5 的
SocialMediaDev Camp。

相关文章