![JavaScript 程序中內(nèi)存泄漏深入理解_第1頁](http://file4.renrendoc.com/view/11b28e80a0ba4af174ca076cf3ba267a/11b28e80a0ba4af174ca076cf3ba267a1.gif)
![JavaScript 程序中內(nèi)存泄漏深入理解_第2頁](http://file4.renrendoc.com/view/11b28e80a0ba4af174ca076cf3ba267a/11b28e80a0ba4af174ca076cf3ba267a2.gif)
![JavaScript 程序中內(nèi)存泄漏深入理解_第3頁](http://file4.renrendoc.com/view/11b28e80a0ba4af174ca076cf3ba267a/11b28e80a0ba4af174ca076cf3ba267a3.gif)
![JavaScript 程序中內(nèi)存泄漏深入理解_第4頁](http://file4.renrendoc.com/view/11b28e80a0ba4af174ca076cf3ba267a/11b28e80a0ba4af174ca076cf3ba267a4.gif)
![JavaScript 程序中內(nèi)存泄漏深入理解_第5頁](http://file4.renrendoc.com/view/11b28e80a0ba4af174ca076cf3ba267a/11b28e80a0ba4af174ca076cf3ba267a5.gif)
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
\o"JavaScript程序中內(nèi)存泄漏深入理解-碼農(nóng)網(wǎng)"JavaScript程序中內(nèi)存泄漏深入理解垃圾回收解放了我們,它讓我們可將精力集中在應(yīng)用程序邏輯(而不是內(nèi)存管理)上。但是,垃圾收集并不神奇。了解它的工作原理,以及如何使它保留本應(yīng)在很久以前釋放的內(nèi)存,就可以實(shí)現(xiàn)更快更可靠的應(yīng)用程序。在本文中,學(xué)習(xí)一種定位JavaScript應(yīng)用程序中內(nèi)存泄漏的系統(tǒng)方法、幾種常見的泄漏模式,以及解決這些泄漏的適當(dāng)方法。簡介當(dāng)處理JavaScript這樣的腳本語言時(shí),很容易忘記每個(gè)對象、類、字符串、數(shù)字和方法都需要分配和保留內(nèi)存。語言和運(yùn)行時(shí)的垃圾回收器隱藏了內(nèi)存分配和釋放的具體細(xì)節(jié)。許多功能無需考慮內(nèi)存管理即可實(shí)現(xiàn),但卻忽略了它可能在程序中帶來重大的問題。不當(dāng)清理的對象可能會(huì)存在比預(yù)期要長得多的時(shí)間。這些對象繼續(xù)響應(yīng)事件和消耗資源。它們可強(qiáng)制瀏覽器從一個(gè)虛擬磁盤驅(qū)動(dòng)器分配內(nèi)存頁,這顯著影響了計(jì)算機(jī)的速度(在極端的情形中,會(huì)導(dǎo)致瀏覽器崩潰)。內(nèi)存泄漏指任何對象在您不再擁有或需要它之后仍然存在。在最近幾年中,許多瀏覽器都改善了在頁面加載過程中從JavaScript回收內(nèi)存的能力。但是,并不是所有瀏覽器都具有相同的運(yùn)行方式。Firefox和舊版的InternetExplorer都存在過內(nèi)存泄漏,而且內(nèi)存泄露一直持續(xù)到瀏覽器關(guān)閉。過去導(dǎo)致內(nèi)存泄漏的許多經(jīng)典模式在現(xiàn)代瀏覽器中以不再導(dǎo)致泄漏內(nèi)存。但是,如今有一種不同的趨勢影響著內(nèi)存泄漏。許多人正設(shè)計(jì)用于在沒有硬頁面刷新的單頁中運(yùn)行的Web應(yīng)用程序。在那樣的單頁中,從應(yīng)用程序的一個(gè)狀態(tài)到另一個(gè)狀態(tài)時(shí),很容易保留不再需要或不相關(guān)的內(nèi)存。在本文中,了解對象的基本生命周期,垃圾回收如何確定一個(gè)對象是否被釋放,以及如何評估潛在的泄漏行為。另外,學(xué)習(xí)如何使用GoogleChrome中的HeapProfiler來診斷內(nèi)存問題。一些示例展示了如何解決\o"閉包"閉包、控制臺(tái)日志和循環(huán)帶來的內(nèi)存泄漏。對象生命周期要了解如何預(yù)防內(nèi)存泄漏,需要了解對象的基本生命周期。當(dāng)創(chuàng)建一個(gè)對象時(shí),JavaScript會(huì)自動(dòng)為該對象分配適當(dāng)?shù)膬?nèi)存。從這一刻起,垃圾回收器就會(huì)不斷對該對象進(jìn)行評估,以查看它是否仍是有效的對象。垃圾回收器定期掃描對象,并計(jì)算引用了每個(gè)對象的其他對象的數(shù)量。如果一個(gè)對象的引用數(shù)量為0(沒有其他對象引用過該對象),或?qū)υ搶ο蟮奈┮灰檬茄h(huán)的,那么該對象的內(nèi)存即可回收。圖1顯示了垃圾回收器回收內(nèi)存的一個(gè)示例。圖1.通過垃圾收集回收內(nèi)存看到該系統(tǒng)的實(shí)際應(yīng)用會(huì)很有幫助,但提供此功能的工具很有限。了解您的JavaScript應(yīng)用程序占用了多少內(nèi)存的一種方式是使用系統(tǒng)工具查看瀏覽器的內(nèi)存分配。有多個(gè)工具可為您提供當(dāng)前的使用,并描繪一個(gè)進(jìn)程的內(nèi)存使用量隨時(shí)間變化的趨勢圖。例如,如果在MacOSX上安裝了XCode,您可以啟動(dòng)Instruments應(yīng)用程序,并將它的活動(dòng)監(jiān)視器工具附加到您的瀏覽器上,以進(jìn)行實(shí)時(shí)分析。在Windows?上,您可以使用任務(wù)管理器。如果在您使用應(yīng)用程序的過程中,發(fā)現(xiàn)內(nèi)存使用量隨時(shí)間變化的曲線穩(wěn)步上升,那么您就知道存在內(nèi)存泄漏。觀察瀏覽器的內(nèi)存占用只能非常粗略地顯示JavaScript應(yīng)用程序的實(shí)際內(nèi)存使用。瀏覽器數(shù)據(jù)不會(huì)告訴您哪些對象發(fā)生了泄漏,也無法保證數(shù)據(jù)與您應(yīng)用程序的真正內(nèi)存占用確實(shí)匹配。而且,由于一些瀏覽器中存在實(shí)現(xiàn)問題,DOM元素(或備用的應(yīng)用程序級對象)可能不會(huì)在頁面中銷毀相應(yīng)元素時(shí)釋放。視頻標(biāo)記尤為如此,視頻標(biāo)記需要瀏覽器實(shí)現(xiàn)一種更加精細(xì)的基礎(chǔ)架構(gòu)。人們曾多次嘗試在客戶端JavaScript庫中添加對內(nèi)存分配的跟蹤。不幸的是,所有嘗試都不是特別可靠。例如,流行的stats.js包由于不準(zhǔn)確性而無法支持。一般而言,嘗試從客戶端維護(hù)或確定此信息存在一定的問題,是因?yàn)樗鼤?huì)在應(yīng)用程序中引入開銷且無法可靠地終止。理想的解決方案是瀏覽器供應(yīng)商在瀏覽器中提供一組工具,幫助您監(jiān)視內(nèi)存使用,識(shí)別泄漏的對象,以及確定為什么一個(gè)特殊對象仍標(biāo)記為保留。目前,只有GoogleChrome(提供了HeapProfile)實(shí)現(xiàn)了一個(gè)內(nèi)存管理工具作為它的開發(fā)人員工具。我在本文中使用HeapProfiler測試和演示JavaScript運(yùn)行時(shí)如何處理內(nèi)存。分析堆快照在創(chuàng)建內(nèi)存泄漏之前,請查看一次適當(dāng)收集內(nèi)存的簡單交互。首先創(chuàng)建一個(gè)包含兩個(gè)按鈕的簡單HTML頁面,如清單1所示。清單1.index.html<html><head><scriptsrc="http:///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是為了確保一種管理事件綁定的簡單語法適合不同的瀏覽器,而且嚴(yán)格遵守最常見的開發(fā)實(shí)踐。為
leaker
類和主要JavaScript方法添加腳本標(biāo)記。在開發(fā)環(huán)境中,將JavaScript文件合并到單個(gè)文件中通常是一種更好的做法。出于本示例的用途,將邏輯放在獨(dú)立的文件中更容易。您可以過濾HeapProfiler來僅顯示特殊類的實(shí)例。為了利用該功能,創(chuàng)建一個(gè)新類來封裝泄漏對象的行為,而且這個(gè)類很容易在HeapProfiler中找到,如清單2所示。清單2.assets/scripts/leaker.jsvarLeaker=function(){};Ltotype={init:function(){}};綁定Start按鈕以初始化
Leaker
對象,并將它分配給全局命名空間中的一個(gè)變量。還需要將Destroy按鈕綁定到一個(gè)應(yīng)清理
Leaker
對象的方法,并讓它為垃圾收集做好準(zhǔn)備,如清單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();現(xiàn)在,您已準(zhǔn)備好創(chuàng)建一個(gè)對象,在內(nèi)存中查看它,然后釋放它。在Chrome中加載索引頁面。因?yàn)槟侵苯訌腉oogle加載jQuery,所以需要連接互聯(lián)網(wǎng)來運(yùn)行該樣例。打開開發(fā)人員工具,方法是打開View菜單并選擇Develop子菜單。選擇
DeveloperTools
命令。轉(zhuǎn)到
Profiles
選項(xiàng)卡并獲取一個(gè)堆快照,如圖2所示。
圖2.Profiles選項(xiàng)卡將注意力返回到Web上,選擇
Start。獲取另一個(gè)堆快照。過濾第一個(gè)快照,查找
Leaker
類的實(shí)例,找不到任何實(shí)例。切換到第二個(gè)快照,您應(yīng)該能找到一個(gè)實(shí)例,如圖3所示。
圖3.快照實(shí)例將注意力返回到Web上,選擇
Destroy。獲取第三個(gè)堆快照。過濾第三個(gè)快照,查找
Leaker
類的實(shí)例,找不到任何實(shí)例。在加載第三個(gè)快照時(shí),也可將分析模式從Summary切換到Comparison,并對比第三個(gè)和第二個(gè)快照。您會(huì)看到偏移值-1(在兩次快照之間釋放了
Leaker
對象的一個(gè)實(shí)例)。萬歲!垃圾回收有效的?,F(xiàn)在是時(shí)候破壞它了。內(nèi)存泄漏1:閉包一種預(yù)防一個(gè)對象被垃圾回收的簡單方式是設(shè)置一個(gè)在回調(diào)中引用該對象的間隔或超時(shí)。要查看實(shí)際應(yīng)用,可更新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");}};現(xiàn)在,當(dāng)重復(fù)
上一節(jié)
中的第1-9步時(shí),您應(yīng)在第三個(gè)快照中看到,Leaker
對象被持久化,并且該間隔會(huì)永遠(yuǎn)繼續(xù)運(yùn)行。那么發(fā)生了什么?在一個(gè)閉包中引用的任何局部變量都會(huì)被該閉包保留,只要該閉包存在就永遠(yuǎn)保留。要確保對
setInterval
方法的回調(diào)在訪問Leaker實(shí)例的范圍時(shí)執(zhí)行,需要將
this
變量分配給局部變量
self,這個(gè)變量用于從閉包內(nèi)觸發(fā)
onInterval。當(dāng)
onInterval
觸發(fā)時(shí),它就能夠訪問Leaker
對象中的任何實(shí)例變量(包括它自身)。但是,只要事件偵聽器存在,Leaker
對象就不會(huì)被垃圾回收。要解決此問題,可在清空所存儲(chǔ)的
leaker
對象引用之前,觸發(fā)添加到該對象的
destroy
方法,方法是更新Destroy按鈕的單擊處理程序,如清單5所示。清單5.assets/scripts/main.js$("#destroy_button").click(function(){leak.destroy();leak=null;});銷毀對象和對象所有權(quán)一種不錯(cuò)的做法是,創(chuàng)建一個(gè)標(biāo)準(zhǔn)方法來負(fù)責(zé)讓一個(gè)對象有資格被垃圾回收。destroy功能的主要用途是,集中清理該對象完成的具有以下后果的操作的職責(zé):阻止它的引用計(jì)數(shù)下降到0(例如,刪除存在問題的事件偵聽器和回調(diào),并從任何服務(wù)取消注冊)。使用不必要的CPU周期,比如間隔或動(dòng)畫。destroy
方法常常是清理一個(gè)對象的必要步驟,但在大多數(shù)情況下它還不夠。在理論上,在銷毀相關(guān)實(shí)例后,保留對已銷毀對象的引用的其他對象可調(diào)用自身之上的方法。因?yàn)檫@種情形可能會(huì)產(chǎn)生不可預(yù)測的結(jié)果,所以僅在對象即將無用時(shí)調(diào)用destroy方法,這至關(guān)重要。一般而言,destroy方法最佳使用是在一個(gè)對象有一個(gè)明確的所有者來負(fù)責(zé)它的生命周期時(shí)。此情形常常存在于分層系統(tǒng)中,比如MVC框架中的視圖或控制器,或者一個(gè)畫布呈現(xiàn)系統(tǒng)的場景圖。內(nèi)存泄漏2:控制臺(tái)日志一種將對象保留在內(nèi)存中的不太明顯的方式是將它記錄到控制臺(tái)中。清單6更新了
Leaker
類,顯示了此方式的一個(gè)示例。清單6.assets/scripts/leaker.jsvarLeaker=function(){};Ltotype={init:function(){console.log("Leakinganobject:%o",this);},destroy:function(){}};可采取以下步驟來演示控制臺(tái)的影響。登錄到索引頁面。單擊
Start。轉(zhuǎn)到控制臺(tái)并確認(rèn)Leaking對象已被跟蹤。單擊
Destroy?;氐娇刂婆_(tái)并鍵入
leak,以記錄全局變量當(dāng)前的內(nèi)容。此刻該值應(yīng)為空。獲取另一個(gè)堆快照并過濾Leaker對象。您應(yīng)留下一個(gè)
Leaker
對象?;氐娇刂婆_(tái)并清除它。創(chuàng)建另一個(gè)堆配置文件。在清理控制臺(tái)后,保留leaker的配置文件應(yīng)已清除??刂婆_(tái)日志記錄對總體內(nèi)存配置文件的影響可能是許多開發(fā)人員都未想到的極其重大的問題。記錄錯(cuò)誤的對象可以將大量數(shù)據(jù)保留在內(nèi)存中。注意,這也適用于:在用戶鍵入JavaScript時(shí),在控制臺(tái)中的一個(gè)交互式會(huì)話期間記錄的對象。由
console.log
和
console.dir
方法記錄的對象。內(nèi)存泄漏3:循環(huán)在兩個(gè)對象彼此引用且彼此保留時(shí),就會(huì)產(chǎn)生一個(gè)循環(huán),如圖4所示。圖4.創(chuàng)建一個(gè)循環(huán)的引用清單7顯示了一個(gè)簡單的代碼示例。清單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對象的實(shí)例化可以修改,如清單8所示。清單8.assets/scripts/main.jsleak=newLeaker();leak.init("leaker1",null);如果在創(chuàng)建和銷毀對象后執(zhí)行一次堆分析,您應(yīng)該會(huì)看到垃圾收集器檢測到了這個(gè)循環(huán)引用,并在您選擇Destroy按鈕時(shí)釋放了內(nèi)存。但是,如果引入了第三個(gè)保留該子對象的對象,該循環(huán)會(huì)導(dǎo)致內(nèi)存泄漏。例如,創(chuàng)建一個(gè)
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
類是讓其他對象向它注冊,然后從注冊表中刪除自身的對象的簡單示例。盡管這個(gè)特殊的類與注冊表毫無關(guān)聯(lián),但這是事件調(diào)度程序和通知系統(tǒng)中的一種常見模式。將該類導(dǎo)入index.html頁面中,放在leaker.js之前,如清單10所示。清單10.index.html<scriptsrc="assets/scripts/registry.js"type="text/javascript"charset="utf-8"></script>更新
Leaker
對象,以向注冊表對象注冊該對象本身(可能用于有關(guān)一些未實(shí)現(xiàn)事件的通知)。這創(chuàng)建了一個(gè)來自要保留的leaker子對象的root節(jié)點(diǎn)備用路徑,但由于該循環(huán),父對象也將保留,如清單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以設(shè)置注冊表,并將對注冊表的一個(gè)引用傳遞給
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. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 基建科工程施工范本合同
- 三農(nóng)村人居環(huán)境整治實(shí)施方案
- 公務(wù)車輛定點(diǎn)維修合同
- 法人向公司借款合同
- 經(jīng)典房地產(chǎn)開發(fā)的合同
- 編程語言高級應(yīng)用作業(yè)指導(dǎo)書
- 養(yǎng)殖業(yè)專業(yè)作業(yè)指導(dǎo)書
- 企業(yè)智能核能技術(shù)與應(yīng)用作業(yè)指導(dǎo)書
- 軟件技術(shù)開發(fā)與測試作業(yè)指導(dǎo)書
- 高港區(qū)二手房買賣合同
- 瓦斯防治八招培訓(xùn)課件
- 刺身行業(yè)趨勢分析
- 《他汀長期治療》課件
- 部編人教版四年級下冊小學(xué)語文全冊教案(教學(xué)設(shè)計(jì))(新課標(biāo)核心素養(yǎng)教案)
- 糖尿病性視網(wǎng)膜病變匯報(bào)演示課件
- 2023第二學(xué)期八年級英語備課組工作總結(jié)
- 國企經(jīng)理層任期制和契約化管理任期制與契約化相關(guān)模板
- 壓力管道檢驗(yàn)員題庫
- 動(dòng)脈采血操作評分標(biāo)準(zhǔn)
- 電力服務(wù)收費(fèi)標(biāo)準(zhǔn)附表
- 小學(xué)主題班會(huì)教學(xué)設(shè)計(jì)-《給你點(diǎn)個(gè)“贊”》通用版
評論
0/150
提交評論