版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
\o"JavaScript程序中内存泄漏深入理解-码农网"JavaScript程序中内存泄漏深入理解垃圾回收解放了我们,它让我们可将精力集中在应用程序逻辑(而不是内存管理)上。但是,垃圾收集并不神奇。了解它的工作原理,以及如何使它保留本应在很久以前释放的内存,就可以实现更快更可靠的应用程序。在本文中,学习一种定位JavaScript应用程序中内存泄漏的系统方法、几种常见的泄漏模式,以及解决这些泄漏的适当方法。简介当处理JavaScript这样的脚本语言时,很容易忘记每个对象、类、字符串、数字和方法都需要分配和保留内存。语言和运行时的垃圾回收器隐藏了内存分配和释放的具体细节。许多功能无需考虑内存管理即可实现,但却忽略了它可能在程序中带来重大的问题。不当清理的对象可能会存在比预期要长得多的时间。这些对象继续响应事件和消耗资源。它们可强制浏览器从一个虚拟磁盘驱动器分配内存页,这显著影响了计算机的速度(在极端的情形中,会导致浏览器崩溃)。内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。在最近几年中,许多浏览器都改善了在页面加载过程中从JavaScript回收内存的能力。但是,并不是所有浏览器都具有相同的运行方式。Firefox和旧版的InternetExplorer都存在过内存泄漏,而且内存泄露一直持续到浏览器关闭。过去导致内存泄漏的许多经典模式在现代浏览器中以不再导致泄漏内存。但是,如今有一种不同的趋势影响着内存泄漏。许多人正设计用于在没有硬页面刷新的单页中运行的Web应用程序。在那样的单页中,从应用程序的一个状态到另一个状态时,很容易保留不再需要或不相关的内存。在本文中,了解对象的基本生命周期,垃圾回收如何确定一个对象是否被释放,以及如何评估潜在的泄漏行为。另外,学习如何使用GoogleChrome中的HeapProfiler来诊断内存问题。一些示例展示了如何解决\o"闭包"闭包、控制台日志和循环带来的内存泄漏。对象生命周期要了解如何预防内存泄漏,需要了解对象的基本生命周期。当创建一个对象时,JavaScript会自动为该对象分配适当的内存。从这一刻起,垃圾回收器就会不断对该对象进行评估,以查看它是否仍是有效的对象。垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的内存即可回收。图1显示了垃圾回收器回收内存的一个示例。图1.通过垃圾收集回收内存看到该系统的实际应用会很有帮助,但提供此功能的工具很有限。了解您的JavaScript应用程序占用了多少内存的一种方式是使用系统工具查看浏览器的内存分配。有多个工具可为您提供当前的使用,并描绘一个进程的内存使用量随时间变化的趋势图。例如,如果在MacOSX上安装了XCode,您可以启动Instruments应用程序,并将它的活动监视器工具附加到您的浏览器上,以进行实时分析。在Windows®上,您可以使用任务管理器。如果在您使用应用程序的过程中,发现内存使用量随时间变化的曲线稳步上升,那么您就知道存在内存泄漏。观察浏览器的内存占用只能非常粗略地显示JavaScript应用程序的实际内存使用。浏览器数据不会告诉您哪些对象发生了泄漏,也无法保证数据与您应用程序的真正内存占用确实匹配。而且,由于一些浏览器中存在实现问题,DOM元素(或备用的应用程序级对象)可能不会在页面中销毁相应元素时释放。视频标记尤为如此,视频标记需要浏览器实现一种更加精细的基础架构。人们曾多次尝试在客户端JavaScript库中添加对内存分配的跟踪。不幸的是,所有尝试都不是特别可靠。例如,流行的stats.js包由于不准确性而无法支持。一般而言,尝试从客户端维护或确定此信息存在一定的问题,是因为它会在应用程序中引入开销且无法可靠地终止。理想的解决方案是浏览器供应商在浏览器中提供一组工具,帮助您监视内存使用,识别泄漏的对象,以及确定为什么一个特殊对象仍标记为保留。目前,只有GoogleChrome(提供了HeapProfile)实现了一个内存管理工具作为它的开发人员工具。我在本文中使用HeapProfiler测试和演示JavaScript运行时如何处理内存。分析堆快照在创建内存泄漏之前,请查看一次适当收集内存的简单交互。首先创建一个包含两个按钮的简单HTML页面,如清单1所示。清单1.index.html<html><head><scriptsrc="///ajax/libs/jquery/1.7.2/jquery.min.js"type="text/javascript"></script></head><body><buttonid="start_button">Start</button><buttonid="destroy_button">Destroy</button><scriptsrc="assets/scripts/leaker.js"type="text/javascript"charset="utf-8"></script><scriptsrc="assets/scripts/main.js"type="text/javascript"charset="utf-8"></script></body></html>包含jQuery是为了确保一种管理事件绑定的简单语法适合不同的浏览器,而且严格遵守最常见的开发实践。为
leaker
类和主要JavaScript方法添加脚本标记。在开发环境中,将JavaScript文件合并到单个文件中通常是一种更好的做法。出于本示例的用途,将逻辑放在独立的文件中更容易。您可以过滤HeapProfiler来仅显示特殊类的实例。为了利用该功能,创建一个新类来封装泄漏对象的行为,而且这个类很容易在HeapProfiler中找到,如清单2所示。清单2.assets/scripts/leaker.jsvarLeaker=function(){};Ltotype={init:function(){}};绑定Start按钮以初始化
Leaker
对象,并将它分配给全局命名空间中的一个变量。还需要将Destroy按钮绑定到一个应清理
Leaker
对象的方法,并让它为垃圾收集做好准备,如清单3所示。清单3.assets/scripts/main.js$("#start_button").click(function(){if(leak!==null||leak!==undefined){return;}leak=newLeaker();leak.init();});$("#destroy_button").click(function(){leak=null;});varleak=newLeaker();现在,您已准备好创建一个对象,在内存中查看它,然后释放它。在Chrome中加载索引页面。因为您是直接从Google加载jQuery,所以需要连接互联网来运行该样例。打开开发人员工具,方法是打开View菜单并选择Develop子菜单。选择
DeveloperTools
命令。转到
Profiles
选项卡并获取一个堆快照,如图2所示。
图2.Profiles选项卡将注意力返回到Web上,选择
Start。获取另一个堆快照。过滤第一个快照,查找
Leaker
类的实例,找不到任何实例。切换到第二个快照,您应该能找到一个实例,如图3所示。
图3.快照实例将注意力返回到Web上,选择
Destroy。获取第三个堆快照。过滤第三个快照,查找
Leaker
类的实例,找不到任何实例。在加载第三个快照时,也可将分析模式从Summary切换到Comparison,并对比第三个和第二个快照。您会看到偏移值-1(在两次快照之间释放了
Leaker
对象的一个实例)。万岁!垃圾回收有效的。现在是时候破坏它了。内存泄漏1:闭包一种预防一个对象被垃圾回收的简单方式是设置一个在回调中引用该对象的间隔或超时。要查看实际应用,可更新leaker.js类,如清单4所示。清单4.assets/scripts/leaker.jsvarLeaker=function(){};Ltotype={init:function(){this._interval=null;this.start();},start:function(){varself=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$("#destroy_button").click(function(){leak.destroy();leak=null;});销毁对象和对象所有权一种不错的做法是,创建一个标准方法来负责让一个对象有资格被垃圾回收。destroy功能的主要用途是,集中清理该对象完成的具有以下后果的操作的职责:阻止它的引用计数下降到0(例如,删除存在问题的事件侦听器和回调,并从任何服务取消注册)。使用不必要的CPU周期,比如间隔或动画。destroy
方法常常是清理一个对象的必要步骤,但在大多数情况下它还不够。在理论上,在销毁相关实例后,保留对已销毁对象的引用的其他对象可调用自身之上的方法。因为这种情形可能会产生不可预测的结果,所以仅在对象即将无用时调用destroy方法,这至关重要。一般而言,destroy方法最佳使用是在一个对象有一个明确的所有者来负责它的生命周期时。此情形常常存在于分层系统中,比如MVC框架中的视图或控制器,或者一个画布呈现系统的场景图。内存泄漏2:控制台日志一种将对象保留在内存中的不太明显的方式是将它记录到控制台中。清单6更新了
Leaker
类,显示了此方式的一个示例。清单6.assets/scripts/leaker.jsvarLeaker=function(){};Ltotype={init:function(){console.log("Leakinganobject:%o",this);},destroy:function(){}};可采取以下步骤来演示控制台的影响。登录到索引页面。单击
Start。转到控制台并确认Leaking对象已被跟踪。单击
Destroy。回到控制台并键入
leak,以记录全局变量当前的内容。此刻该值应为空。获取另一个堆快照并过滤Leaker对象。您应留下一个
Leaker
对象。回到控制台并清除它。创建另一个堆配置文件。在清理控制台后,保留leaker的配置文件应已清除。控制台日志记录对总体内存配置文件的影响可能是许多开发人员都未想到的极其重大的问题。记录错误的对象可以将大量数据保留在内存中。注意,这也适用于:在用户键入JavaScript时,在控制台中的一个交互式会话期间记录的对象。由
console.log
和
console.dir
方法记录的对象。内存泄漏3:循环在两个对象彼此引用且彼此保留时,就会产生一个循环,如图4所示。图4.创建一个循环的引用清单7显示了一个简单的代码示例。清单7.assets/scripts/leaker.jsvarLeaker=function(){};Ltotype={init:function(name,parent){this._name=name;this._parent=parent;this._child=null;this.createChildren();},createChildren:function(){if(this._parent!==null){//Onlycreateachildifthisistherootreturn;}this._child=newLeaker();this._child.init("leaker2",this);},destroy:function(){}};Root对象的实例化可以修改,如清单8所示。清单8.assets/scripts/main.jsleak=newLeaker();leak.init("leaker1",null);如果在创建和销毁对象后执行一次堆分析,您应该会看到垃圾收集器检测到了这个循环引用,并在您选择Destroy按钮时释放了内存。但是,如果引入了第三个保留该子对象的对象,该循环会导致内存泄漏。例如,创建一个
registry
对象,如清单9所示。清单9.assets/scripts/registry.jsvarRegistry=function(){};Rtotype={init:function(){this._subscribers=[];},add:function(subscriber){if(this._subscribers.indexOf(subscriber)>=0){//Alreadyregisteredsobailoutreturn;}this._subscribers.push(subscriber);},remove:function(subscriber){if(this._subscribers.indexOf(subscriber)<0){//Notcurrentlyregisteredsobailoutreturn;}this._subscribers.splice(this._subscribers.indexOf(subscriber),1);}};registry
类是让其他对象向它注册,然后从注册表中删除自身的对象的简单示例。尽管这个特殊的类与注册表毫无关联,但这是事件调度程序和通知系统中的一种常见模式。将该类导入index.html页面中,放在leaker.js之前,如清单10所示。清单10.index.html<scriptsrc="assets/scripts/registry.js"type="text/javascript"charset="utf-8"></script>更新
Leaker
对象,以向注册表对象注册该对象本身(可能用于有关一些未实现事件的通知)。这创建了一个来自要保留的leaker子对象的root节点备用路径,但由于该循环,父对象也将保留,如清单11所示。清单11.assets/scripts/leaker.jsvarLeaker=function(){};Ltotype={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){//Onlycreatechildifthisistherootreturn;}this._child=newLeaker();this._child.init("leaker2",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(){varleakExists=!( window["leak"]===null||window["leak"]===undefined );if(leakExists){return;}leak=newLeaker();leak.init("leaker1",null,registry);});$("#destroy_button").click(function(){leak.destroy();leak=null;});regist
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 帮朋友贷款买车的合同3篇
- 数据库租赁与信息服务合同3篇
- 教育学工作合同模板3篇
- 携程旅行合同电子版3篇
- 教育培训导游劳动合同样本3篇
- 工业生产设备采购合同3篇
- 文化传媒劳务合同范本3篇
- 帝尔婚庆服务合同的适用范围3篇
- 房屋买卖委托合同3篇
- 旅游合同与实际行程不符3篇
- UI设计·形考任务一
- 论文《后疫情时代信息技术与幼儿园教育深度融合的策略研究》
- 2023-2024学年江西省南昌市数学六年级第一学期期末复习检测模拟试题含答案
- 医院不担当、不作为问题专项治理实施方案
- 体外诊断试剂盒风险分析报告
- -2023广东高考英语听说考试三问整理
- 9高考语文透析一题·诗歌鉴赏(手法技巧)《柳梢青 送卢梅坡 》
- 如何高效学习学习通超星课后章节答案期末考试题库2023年
- DB3205T 1062-2023 制造业质量管理数字化水平评价规范
- 贵州省建筑节能工程认定表
- 退出协议书(8篇)
评论
0/150
提交评论