摘要:內存回收內存泄漏前言最近在細讀高級程序設計,對于我而言,中文版,書中很多地方一筆帶過,所以用自己所理解的,嘗試細致解讀下。內存回收在談內存泄漏之前,首先,先了解下的內存回收機制。
內存回收 && 內存泄漏
前言:最近在細讀Javascript高級程序設計,對于我而言,中文版,書中很多地方一筆帶過,所以用自己所理解的,嘗試細致解讀下。如有紕漏或錯誤,會非常感謝您的指出。文中絕大部分內容引用自《JavaScript高級程序設計第三版》。
內存回收在談“內存泄漏”之前,首先,先了解下JavaScript的內存回收機制。
JavaScript具有內存自動回收機制,也就是說,執(zhí)行環(huán)境會負責管理代碼執(zhí)行過程中使用的內存。
而在C和C++之類的語言中,開發(fā)人員的一項基本任務就是手動跟蹤內存的使用情況,這是造成許多問題的根源。
在編寫JavaScript程序時,開發(fā)人員不用再關心內存使用問題,所需內存的分配以及所用內存的回收,完全實現了自動管理。
這種內存回收機制的原理,其實很簡單。即:找出那些不再繼續(xù)使用的變量,然后釋放其占用的內存。
內存回收器會按照固定的時間間隔(或代碼執(zhí)行中預定的收集時間), 周期性地執(zhí)行這一操作。
回顧下, 函數中局部變量的正常生命周期。
局部變量只在函數執(zhí)行的過程中存在。而在這個過程中,會為局部變量在棧(或堆)內存上分配相應的空間,以便存儲它們的值。
然后在函數中使用這些變量,直至函數執(zhí)行結束。
此時,局部變量就沒有存在的必要了,因此可以釋放它們的內存以供將來使用。
在這種情況下,很容易判斷變量是否還有存在的必要了。
但是,實際情況卻很復雜,不是那么容易得出結論的。
內存回收器必須跟蹤哪個變量有用,哪個變量沒用。
對于不再有用的變量打上標記,以備將來回收其占用的內存。
用于標識無用變量的策略可能會因實現而異,具體到瀏覽器中的實現,則通常有兩種策略。
標記清除策略JavaScript中最常用的內存回收方式是標記清除(mark-and-sweep)。當變量進入環(huán)境(例如:在函數中聲明一個變量)時,就將這個變量標記為“進入環(huán)境”。
從邏輯上講,永遠不能釋放進入執(zhí)行環(huán)境的變量所占用的內存,只要執(zhí)行流進入相應的環(huán)境,就可能會用到它們。
而當變量離開環(huán)境時,則將其標記為“離開環(huán)境”。
可以使用任何方式來標記變量。比如,可以通過翻轉某個特殊的位來記錄一個變量何時進入環(huán)境,或者使用一個“進入環(huán)境的”變量列表以及一個“離開環(huán)境的”變量列表,來跟蹤變量。說到底,如何標記變量其實不重要,關鍵在于采取什么策略。
內存回收器在運行的時候會給存儲在內存中的所有變量都加上標記(當然,可以使用任何標記方式)。
然后,它會去掉環(huán)境中正在引用的變量的標記(標記意味著要被回收)。
而后,再被加上標記的變量被視為準備回收,因為環(huán)境中的變量已經無法訪問到這些變量了。
最后,內存回收器完成內存回收工作,銷毀那些帶標記的值,并回收它們所占用的內存空間。
IE、Firefox、Opera、Chrome和Safari的JavaScript內存回收,使用的都是標記清除氏的內存回收策略,只不過內存回收的時間間隔互有不同。
引用計數這一部分可稍作了解
另一種不太常見的內存回收策略,引用計數(reference counting)。
引用計數的含義是跟蹤記錄每個值被引用的次數。
當聲明了一個變量并將一個引用類型值賦給該變量時,則這個值的引用次數就是1。
如果同一個值又被賦給另外一個變量,則該值的引用次數加1。
相反,如果包含對這個值引用的變量又取得了另外一個值,則這個值的引用次數減1。
當這個值的引用次數變成0時,則說明沒有辦法再訪問這個值了,因而可以將其占用的內存空間回收。
當內存回收器再次運行時,它就會釋放那些引用次數為零的值所占用的內存。
Netscape Navigator 3.0是最早使用引用計數策略的瀏覽器,但很快它就遇到一個嚴重的問題:循環(huán)引用。
循環(huán)引用指的是對象A中包含一個指向對象B的指針,而對象B中也包含一個指向對象A的引用。
function referenceCountingProblem () { //調用函數并執(zhí)行的話 var objectA = new Object(); // objectA引用值的reference counting 為1 var objectB = new Object(); // objectB引用值的reference counting 為1 objectA.otherObject = objectB; // 現在objectB引用值的reference counting為2 objectB.anotherObject = objectA; // 現在objectA引用值的reference counting為2 }
在這個例子中,objectA和objectB通過各自的屬性相互引用;
這兩個對象的引用次數都是2。
在采用標記清除策略的實現中,由于函數執(zhí)行之后,這兩個對象都離開了作用域,因此這種相互引用不是個問題。
但在采用引用計數策略的實現中,當函數執(zhí)行完畢后,objectA和objectB還將繼續(xù)存在,因為它們的引用次數永遠不是0。
假如這個函數被重復多次調用,就會導致大量內存得不到回收。 為此,Netscape在Navigator4.0中放棄了引用計數策略,
轉而采用標記清除(mark-and-sweep)來實現其內存回收機制。
可是,引用計數導致的麻煩并為就此終結。
IE中有一部分對象并不是原生JavaScript對象。 例如,BOM和DOM中的對象就是使用C++以COM對象(Component Object Model,組件對象模型)的形式實現的,而COM對象的內存回收機制采用的就是引用計數策略。
即使IE的JavaScript引擎是使用標記清除策略來實現的,但JavaScript訪問的COM對象依然是基于引用計數策略的。
只要在IE中涉及COM對象,就會存在循環(huán)引用的問題。
var element = document.getElementById("some_element"); var myObject = new Object(); myObject.element = element; // 原生JS對象引用著DOM對象 element.someObject = myObject; // DOM對象引用著JS對象
以上代碼,在一個DOM元素(element)和一個原生JavaScript對象(myObject)之間創(chuàng)建了循環(huán)引用。
其中,變量myObject有一個名為element的屬性指向element對象,而變量element也有一個屬性名叫someObject回指myObject。
由于存在這個循環(huán)引用,即使將例子中的DOM從頁面中移出,它也永遠不會回收。
為了避免這樣的循環(huán)引用問題,最好是在不適用它們的時候手工斷開原生JavaScript對象與DOM元素之間的連接。
myObject.element = null; element.someObject = null;
將變量設置為null意味著切斷變量與它此前引用的值之間的連接。當內存回收器再次運行時,就會刪除這些值并回收它們占用的內存。
為了解決上述問題,IE9把BOM和DOM對象都轉換成真正的JavaScript對象。
這樣,就避免了兩種內存回收算法并存導致的問題,也消除了常見的內存的泄漏問題。
由于IE9之前的版本對JScript對象和COM對象使用不同的內存回收算法(策略)。
因此,閉包在IE的這些版本中會導致一些特殊的問題,具體來說,如果閉包的作用域鏈中保存著一個HTML元素,那么就意味著該元素無法被銷毀。
function handler() { var element = document.getElementById("someElement"); element.onclick = function() { alert(element.id); } }
以上代碼創(chuàng)建了一個座位element元素事件處理程序的閉包,而這個閉包則又創(chuàng)建了一個循環(huán)引用。
由于匿名函數保存了一個對hander()的活動對象的引用,因此就會導致無法減少element的引用數。
只要匿名函數存在,element的引用數至少也是1,因此,它所占用的內存就永遠不會被回收。
不過,這個問題可通過稍微改寫一下代碼來解決。
function handler(){ var element = document.getElementById("someElement"); var id = element.id; element.onclick = function(){ console.log(id); }; element = null; }
在范例代碼中,通過把element.id的一個副本保存在一個變量中,并且在閉包中引用該變量消除循環(huán)引用。
但僅僅做到這一步,還是不能解決內存泄漏的問題。
必須記住:閉包會引起包含函數的整個活動對象,而其中包含著element。
即使閉包不直接引用element,包含函數的活動對象中仍然會保存一個引用。
因此,有必要把element變量設置為null。
這樣就能夠解除對DOM對象的引用,順利地減少其引用數,確保正常回收其占用的內存。
關于這里的闡述,我有不同的看法。 既然閉包引用這個變量,說明這個變量,是我們需要用到的,某種意義上說,這不是“內存泄漏”!!
內存回收導致的性能問題(IE)此部分也可稍作了解,當然知道這些歷史,也會更加明白為啥都使用標記清除策略
內存回收器是周期性運行的,如果為變量分配的內存數量很客觀,那么回收工作量也是很大的。
在這種情況下,確定內存回收的時間間隔是一個非常重要的問題。
說到內存回收器多長時間運行一次,不禁讓人聯想到IE因此而聲名狼藉的性能問題。
IE的內存回收器是根據內存分配量運行的,具體一點說就是256變量||4096個對象(或數組)字面量 和數組元素(slot)|| 64KB的字符竄。
達到上述任何一個臨界值,內存回收器就會運行。
這種實現方式的問題在于,一個腳本中本來就包含那么多變量,那么該腳本很可能會在其生命周期中一直保有那么多的變量。
而這樣一來,內存回收器,就不得不頻繁的運行。 就引發(fā)了嚴重的性能問題。 促使IE7重寫了其內存回收策略。
到IE7,其JavaScript引擎的內存回收的實現改變了方式:觸發(fā)內存回收的變量分配、字面量或數組元素的臨界值被調整為動態(tài)修正。
IE7中的各項臨界值在初始時與IE6相等。如果內存回收過程中,回收的內存分配量低于15%,則變量、字面量或數組元素的臨界值就會加倍。
這也說明,絕大多數變量是被引用著的,內存回收的臨界值太低,需要往上調。
如果內存回收了85%的內尺寸分配量,則將各種臨界值重置回默認值。
這一看似簡單的調整,極大地提升了IE在運行包含大量JavaScript的頁面時的性能。
事實上,在有的游覽器紅可以觸發(fā)內存回收,但是不建議這么做。在IE中,調用window.CollectGarbage()方法會立即執(zhí)行內存回收。在Opera7及更高版本中,調用window.opera.collect()也會啟動內存回收。
管理內存使用具備內存回收機制的語言編寫程序,開發(fā)人員一般不必擔心內存管理的問題。
但是,JavaScript在進行內存管理及內存回收面臨的問題還是有點與眾不同。
其中,最主要的一個問題,就是分配給Web瀏覽器的可用內存數量通常比分配給桌面應用程序的少。
這樣做的目的是處于安全方面的考慮, 目的是防止運行JavaScript的網頁耗盡全部系統(tǒng)內存而導致系統(tǒng)奔潰。
內存限制問題不僅會影響給變量分配內存,同時還會影響調用棧以及在一個線程中能夠同時執(zhí)行的語句數量。
因此,確保占用最少的內存可以讓頁面獲得更好的性能。優(yōu)化內存占用的最佳方式,就是為執(zhí)行中的代碼只保存必要的數據。
一旦數據不再有用,最好通過將其值設置為null來釋放其引用——這個做法叫做接觸引用(dereferencing)。
這一做法適用于大多數全局變量和全局對象的屬性。
局部變量會在它們離開執(zhí)行環(huán)境時自動被解除引用。
function createPerson(name) { var localPerson = new object(); localPerson.name = name; return localPersonl; } var globalPerson = createPerson("Shaw"); //手工解除globalPerson的引用 globalPerson = null;
變量globalPerson取得了createPerson()函數返回的值。
在createPerson()函數內部,我們創(chuàng)建了一個對象并將其賦給局部變量localPerson,然后又為該對象添加了一個名為name的屬性。
最后,當調用這個函數時,localPerson以函數值的形式返回并賦給全局變量globalPerson。
由于localPerson在createPerson()函數執(zhí)行完畢后就離開了其執(zhí)行環(huán)境,因此,無需我們顯式地為它解除引用。
但是對于全局變量globalPerson而言,則需要我們在不使用它的時候手工為它解除引用,這也是上面例子中,最后一行代碼的意義。
解除一個值的引用并不意味著自動回收該值所占用的內存。
解除引用的真正作用是讓值脫離執(zhí)行環(huán)境,以便內存回收器下次運行時將其回收。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98819.html
JavaScript在創(chuàng)建變量(數組、字符串、對象等)是自動進行了分配內存,而且當它沒有被使用的狀態(tài)下,會自動的釋放分配的內容;其實這樣基層語言,如C語言,他們提供了內存管理的接口,比如malloc()用于分配所需的內存空間、free()釋放之前所分配的內存空間。 釋放內存的過程稱為垃圾回收,例如avaScript這類高級語言可以提供了內存自動分配和自動回收,其實這個自動儲存不會占用太多空間...
摘要:該對象包含了函數的所有局部變量命名參數參數集合以及,然后此對象會被推入作用域鏈的前端。如果整個作用域鏈上都無法找到,則返回。此時的作用域鏈包含了兩個對象的活動對象和對象。 前端學習:教程&開發(fā)模塊化/規(guī)范化/工程化/優(yōu)化&工具/調試&值得關注的博客/Git&面試-前端資源匯總 歡迎提issues斧正:閉包 JavaScript-閉包 閉包(closure)是一個讓人又愛又恨的somet...
摘要:需要校驗字節(jié)信息是否符合規(guī)范,避免惡意信息和不規(guī)范數據危害運行安全。具有相同哈希值的鍵值對會組成鏈表。通過在協議下添加了一層協議對數據進行加密從而保證了安全。常見的非對稱加密包括等。 類加載過程 Java 中類加載分為 3 個步驟:加載、鏈接、初始化。 加載。 加載是將字節(jié)碼數據從不同的數據源讀取到JVM內存,并映射為 JVM 認可的數據結構,也就是 Class 對象的過程。數據源可...
摘要:今天同學去面試,做了兩道面試題全部做錯了,發(fā)過來給道典型的面試題前端掘金在界中,開發(fā)人員的需求量一直居高不下。 排序算法 -- JavaScript 標準參考教程(alpha) - 前端 - 掘金來自《JavaScript 標準參考教程(alpha)》,by 阮一峰 目錄 冒泡排序 簡介 算法實現 選擇排序 簡介 算法實現 ... 圖例詳解那道 setTimeout 與循環(huán)閉包的經典面...
閱讀 1835·2021-11-11 16:55
閱讀 757·2019-08-30 15:53
閱讀 3597·2019-08-30 15:45
閱讀 742·2019-08-30 14:10
閱讀 3272·2019-08-30 12:46
閱讀 2130·2019-08-29 13:15
閱讀 2033·2019-08-26 13:48
閱讀 941·2019-08-26 12:23