>

应用程序中的内存泄漏

- 编辑:至尊游戏网站 -

应用程序中的内存泄漏

理解 JavaScript 应用程序中的内部存款和储蓄器泄漏

2015/02/02 · JavaScript · Javascript, 内部存款和储蓄器泄漏

原来的小说出处: IBM developerworks   

简介

当管理 JavaScript 那样的脚本语言时,超级轻易忘记各种对象、类、字符串、数字和章程都要求分配和保存内部存款和储蓄器。语言和平运动行时的饭桶回笼器遮掩了内部存款和储蓄器分配和刑满释放解除劳教的现实细节。

不少功力没有必要思考内部存款和储蓄器管理就可以实现,但却忽略了它或者在前后相继中带给重大的题目。不当清理的靶子也许会存在比预期要长得多的岁月。那个目的继续响应事件和消耗电源。它们可强制浏览器从二个设想磁盘驱动器分配内部存款和储蓄器页,那鲜明影响了Computer的速度(在最棒的情形中,会招致浏览器崩溃卡塔 尔(英语:State of Qatar)。

内存泄漏指任何对象在你不再持有或索要它未来依旧存在。在日前几年中,非常多浏览器都精雕细刻了在页面加载进度中从 JavaScript 回笼内部存款和储蓄器的力量。但是,并非有所浏览器都怀有同等的运维格局。Firefox 和旧版的 Internet Explorer 都留存过内部存款和储蓄器泄漏,并且内部存款和储蓄器走漏一向不停到浏览器关闭。

千古引致内部存款和储蓄器泄漏的不在少数优秀形式在今世浏览器中以不再引致泄漏内部存款和储蓄器。但是,方今有大器晚成种不一样的取向影响着内部存款和储蓄器泄漏。许四个人正规划用来在未有硬页面刷新的单页中运作的 Web 应用程序。在那么的单页中,从应用程序的一个场地到另贰个动静时,比较轻巧保留不再供给或不相干的内部存储器。

在本文中,理解对象的主干生命周期,垃圾回笼怎样规定三个对象是还是不是被假释,甚至怎么样评估潜在的败露行为。别的,学习怎么着利用 谷歌 Chrome 中的 Heap Profiler 来确诊内慰劳题。一些示范浮现了什么样缓和闭包、调节台日志和循环带给的内存泄漏。

您可下载本文中利用的身体力行的源代码。

对象生命周期

要询问什么防守内存泄漏,要求驾驭对象的主干生命周期。当创制贰个目的时,JavaScript 会自动为该对象分配适当的内存。从这一刻起,垃圾回笼器就能不断对该指标开展评估,以查看它是还是不是仍然是卓有效用的对象。

污源回笼器定期扫描对象,并寻思援用了各类对象的别的对象的数额。若是二个目的的引用数量为 0(未有任何对象援用过该目的卡塔 尔(阿拉伯语:قطر‎,或对该对象的无可比拟引用是循环的,那么该指标的内部存款和储蓄器就能够回笼。图 1 体现了垃圾堆回笼器回笼内部存款和储蓄器的二个演示。

图 1. 透过垃圾搜集回笼内存

图片 1

见到该种类的实在利用会很有帮扶,但提供此作用的工具很单薄。领会你的 JavaScript 应用程序占用了不怎么内部存款和储蓄器的风度翩翩种方法是选取系统工具查看浏览器的内部存款和储蓄器分配。有多少个工具可为您提供当前的行使,并勾画三个历程的内部存款和储蓄器使用量随即间变化的方向图。

诸如,假诺在 Mac OSX 上安装了 XCode,您能够运营 Instruments 应用程序,并将它的位移监视器工具附加到您的浏览器上,以扩充实时剖判。在 Windows上,您能够应用任务微机。借使在你使用应用程序的历程中,发掘内部存储器使用量任何时候间变化的曲线稳步上涨,那么您就清楚存在内部存款和储蓄器泄漏。

考查浏览器的内部存款和储蓄器占用只好相当的粗略地呈现 JavaScript 应用程序的实际上内部存款和储蓄器使用。浏览器数据不会告诉您哪些对象发生了泄漏,也无计可施有限扶助数据与你应用程序的实在内存占用确实相称。并且,由于一些浏览器中设有贯彻难点,DOM 成分(或备用的利用程序级对象卡塔 尔(阿拉伯语:قطر‎可能不会在页面中销毁相应成分时释放。录制标识尤为如此,录制标识供给浏览器达成风姿洒脱种越来越精细的底工架构。

公众曾多次尝试在客户端 JavaScript 库中增加对内部存款和储蓄器分配的追踪。不幸的是,全体尝试都不是特地可信赖。举个例子,流行的 stats.js 包由于禁止确性而不可超出支撑。日常来说,尝试从客户端维护或规定此音信存在必然的难题,是因为它会在应用程序中引进开销且不能可相信地休息。

大好的消除方案是浏览器代理商在浏览器中提供黄金年代组织工作具,帮忙你监视内部存款和储蓄器使用,识别泄漏的靶子,以至鲜明为何四个奇特目标仍标志为保留。

现阶段,唯有 谷歌(Google卡塔尔 Chrome(提供了 Heap Profile卡塔 尔(阿拉伯语:قطر‎落成了二个内部存款和储蓄器管理工科具作为它的开垦职工作者具。小编在本文中选用 Heap Profiler 测量检验和演示 JavaScript 运营时怎么管理内部存款和储蓄器。

浅析堆快速照相

在开创内部存款和储蓄器泄漏在此以前,请查看二回适当搜罗内部存款和储蓄器的简易交互作用。首先创制多少个包括三个按键的粗略 HTML 页面,如项目清单 1 所示。

清单 1. index.html

XHTML

<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>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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类和要害 JavaScript 方法增添脚本标志。在付出境况中,将 JavaScript 文件合併到单个文件中多如牛毛是大器晚成种越来越好的做法。出于本示例的用项,将逻辑放在独立的文件中更便于。

您能够过滤 Heap Profiler 来仅显示特殊类的实例。为了接收该成效,成立三个新类来封装泄漏对象的表现,而且这几个类超级轻便在 Heap Profiler 中找到,如清单 2 所示。

清单 2. assets/scripts/leaker.js

JavaScript

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

1
2
3
4
5
6
var Leaker = function(){};
Leaker.prototype = {
    init:function(){
 
    }    
};

绑定 Start 开关以初步化Leaker目的,并将它分配给全局命名空间中的多个变量。还索要将 Destroy 开关绑定到一个应清理Leaker对象的措施,并让它为垃圾搜集做好希图,如项目清单 3 所示。

清单 3. assets/scripts/main.js

JavaScript

$("#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
2
3
4
5
6
7
8
9
10
11
12
13
$("#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 中加载索引页面。因为你是一向从 Google 加载 jQuery,所以必要连接网络来运营该样例。
  2. 开垦开荒人士工具,方法是张开 View 菜单并精选 Develop 子菜单。选拔Developer Tools命令。
  3. 转到Profiles选项卡并获取一个堆快速照相,如图 2 所示。

    ##### 图 2. Profiles 选项卡

    图片 2

  4. 将注意力再次来到到 Web 上,接收Start。

  5. 获取另三个堆快速照相。
  6. 过滤第二个快速照相,查找Leaker类的实例,找不到此外实例。切换成第一个快速照相,您应该能找到三个实例,如图 3 所示。

    ##### 图 3. 快速照相实例

    图片 3

  7. 将集中力重临到 Web 上,接受Destroy。

  8. 赢得第五个堆快速照相。
  9. 过滤第多个快速照相,查找Leaker类的实例,找不到此外实例。在加载第七个快速照相时,也可将解析方式从 Summary 切换来 Comparison,并对照第八个和第3个快速照相。您会看出偏移值 -1(在五回快速照相之间自由了Leaker对象的一个实例卡塔尔。

皇帝!垃圾回笼有效的。今后是时候破坏它了。

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

生机勃勃种堤防一个对象被垃圾回笼的简约方法是设置三个在回调中援用该指标的间隔或过期。要翻开实际运用,可更新 leaker.js 类,如项目清单 4 所示。

清单 4. assets/scripts/leaker.js

JavaScript

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"); } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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");
    }
};

前日,当再次上风姿洒脱节中的第 1-9 步时,您应在第多少个快速照相中来看,Leaker对象被持久化,并且该间隔会永远继续运行。那么发生了什么?在一个闭包中引用的任何局部变量都会被该闭包保留,只要该闭包存在就永远保留。要确保对setInterval方法的回调在访问 Leaker 实例的范围时执行,需要将this变量分配给局部变量self,那个变量用于从闭包内触发onInterval。当onInterval触发时,它就能够访谈Leaker对象中的任何实例变量(包括它自身)。但是,只要事件侦听器存在,Leaker对象就不会被垃圾回收。

要解决此主题材料,可在清空所蕴藏的leaker对象援引从前,触发增多到该目的的destroy方法,方法是改过Destroy 按键的单击管理程序,如清单 5 所示。

清单 5. assets/scripts/main.js

JavaScript

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

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

销毁对象和对象全部权

大器晚成种科学的做法是,创制一个标准方法来担负让一个目的有身份被垃圾回笼。destroy 功能的首要用项是,聚焦清理该目的完成的有所以下后果的操作的职务:

  • 阻止它的引用计数下跌到0(举个例子,删除存在难题的事件侦听器和回调,并从任何服务打消注册卡塔尔国。
  • 行使没有必要的 CPU 周期,比方间距或动画。

destroy方法常常是清理一个对象的必要步骤,但在大多数情况下它还不够。在理论上,在销毁相关实例后,保留对已销毁对象的引用的其他对象可调用自身之上的方法。因为这种情形可能会产生不可预测的结果,所以仅在对象即将无用时调用 destroy 方法,这至关重要。

日常来讲,destroy 方法最好使用是在叁个指标有五个醒指标主人来顶住它的生命周期时。此景况日常存在于分层系统中,比如MVC 框架中的视图或调整器,也许二个画布彰显系统的场景图。

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

一种将目的保留在内部存款和储蓄器中的不太明显的法门是将它记录到调整桃园。清单 6 更新了Leaker类,显示了此办法的三个示范。

清单 6. assets/scripts/leaker.js

JavaScript

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

1
2
3
4
5
6
7
8
9
10
11
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 对象。您应预先流出一个Leaker对象。
  7. 回去调整台并免去它。
  8. 成立另贰个堆配置文件。在清理调节台后,保留 leaker 的安排文件应已废除。

调整台日志记录对全部内部存储器配置文件的熏陶大概是得寸进尺开垦职员都未想到的极度主要的标题。记录错误的目的能够将大批量数码保存在内部存款和储蓄器中。注意,那也适用于:

  • 在客商键入 JavaScript 时,在调整新北的壹人机联作式会话时期记录的对象。
  • 由console.log和console.dir方法记录的靶子。

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

在五个目的相互援引且相互保留时,就能够产生叁个循环,如图 4 所示。

图 4. 创立三个巡回的引用

图片 4

项目清单 7 展现了三个归纳的代码示例。

清单 7. assets/scripts/leaker.js

JavaScript

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(){ } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

JavaScript

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

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

如若在创立和销毁对象后施行三回堆剖判,您应该拜会到垃圾搜集器检查实验到了这几个轮回援引,并在你选用Destroy 按键时释放了内部存款和储蓄器。

但是,固然引进了第多个保留该子对象的靶子,该循环会引致内部存款和储蓄器泄漏。举个例子,创立一个registry对象,如清单9 所示。

清单 9. assets/scripts/registry.js

JavaScript

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 ); } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

XHTML

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

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

履新Leaker对象,以向注册表对象注册该指标自己(可能用于有关部分未兑现事件的通报卡塔 尔(阿拉伯语:قطر‎。这创制了叁个起点要保存的 leaker 子对象的 root 节点备用路线,但出于该循环,父对象也将保留,如清单11 所示。

清单 11. assets/scripts/leaker.js

JavaScript

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); } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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

JavaScript

$("#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();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$("#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. 由于保留援引招致的内部存储器泄漏

图片 5

从外表上看,它像叁个不自然的言传身教,但它实在特别普及。越发优良的面向对象框架中的事件侦听器日常固守近似图 5 的形式。那连串型的方式也说不佳与闭包和调控台日志招致的主题材料相关联。

固然有多样措施来解决此类主题材料,但在那情状下,最轻便易行的点子是翻新Leaker类,以在销毁它时销毁它的子对象。对于本示例,更新destroy方法(如清单 13 所示)就足够了。

清单 13. assets/scripts/leaker.js

JavaScript

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

1
2
3
4
5
6
destroy: function(){
    if(this._child !== null){
        this._child.destroy();            
    }
    this._registry.remove(this);
}

不常候,四个还未有丰富紧凑关联的对象时期也会设有循环,此中一个指标管理另四个对象的生命周期。在这里样的意况下,在此三个对象时期创造关联的靶子应负担在和谐被灭亡时停顿循环。

结束语

就是 JavaScript 已被垃圾回笼,依然会有不知凡几行法会将无需的靶子保留在内部存款和储蓄器中。近年来超越52%浏览器都已经济体修改了内部存储器清理效用,但评估您应用程序内部存款和储蓄器堆的工具依然有限(除了使用 GoogleChrome卡塔 尔(阿拉伯语:قطر‎。通过从轻易的测量检验案例初叶,比较轻便评估潜在的走漏行为并分明是还是不是留存走漏。

不通过测验,就不容许正确衡量内部存款和储蓄器使用。超轻易使循环援引吞并对象曲线图中的超过八分之四区域。Chrome 的 Heap Profiler 是多个确诊内部存款和储蓄器难题的可贵工具,在支付时依期接受它也是三个不利的选项。在推测指标曲线图中要自由的切实可行能源时请设定具体的预期,然后进行认证。任何时候当您看看不想要的结果时,请留神考验。

在创设对象时要安顿该对象的清管事人业,那比在今后将一个清理阶段移植到应用程序中要轻巧得多。日常要安顿删除事件侦听器,并终止您创造的间隔。借使意识到了您应用程序中的内部存款和储蓄器使用,您将获取更牢靠且质量更加高的应用程序。

下载

描述 名字 大小
文章源代码 JavascriptMemoryManagementSource.zip 4KB

参谋资料

学习

  • Chrome Developer Tools: Heap Profiling:依附此教程学习怎么利用 Heap Profiler 揭发你的应用程序中的内部存款和储蓄器泄漏。
  • “JavaScript 中的内部存储器泄漏格局”(developerWorks,二零零七年 4 月卡塔 尔(阿拉伯语:قطر‎:了然 JavaScript 中的循环援用的基本知识,以至为什么它们会在一些浏览器中掀起难点。
  • “探究内部存款和储蓄器泄漏”:了然尽管在反复解源代码的情景下也足以轻易地确诊泄漏的不二秘籍。
  • “JavaScript 内部存款和储蓄器泄漏”:领会关于内部存款和储蓄器泄漏的始末和检查实验的更加多音信。
  • “avaScript and the Document Object Model”(developerWorks,二零零二年 7 月卡塔 尔(阿拉伯语:قطر‎:精通 JavaScript 的 DOM 方法,以致怎么着塑造贰个能够让顾客增加备注和和编排备注内容的网页。
  • A re-introduction to JavaScript:更详细地打听 JavaScript 及其特点。
  • developerWorks Web 开垦专区:查找涉及各个基于 Web 的缓慢解决方案的小说。访谈Web 开采技巧库,查阅丰硕的才干作品,以致技能、教程、规范和 IBM 红皮书。
  • developerWorks 技艺活动和互连网广播:任何时候关注这一个会议中的本领。
  • developerWorks 点播演示:旁观丰盛的示范,满含面向初读书人的出品设置和设置,以致为经历丰富的开拓职员提供的高等成效。
  • Twitter 上的 developerWorks:立刻投入以关爱 developerWorks 推文。

得到付加物和才能

  • 开拓职员频道:获取 Google Chrome 版本以致最新的 Developer Tools 版本。
  • IBM 产物评估版:下载或浏览 IBM SOA 沙盒中的在线教程,亲自使用来源 DB2、Lotus、Rational、Tivoli 和 WebSphere 的应用程序开垦工具和中间件付加物。

讨论

  • developerWorks 社区:查看开垦职员拉动的博客、论坛、群组和维基,并与其他developerWorks 顾客调换。

    赞 3 收藏 评论

图片 6

本文由技术教程发布,转载请注明来源:应用程序中的内存泄漏