成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript之內(nèi)存回收&&內(nèi)存泄漏

dayday_up / 628人閱讀

摘要:內(nèi)存回收內(nèi)存泄漏前言最近在細讀高級程序設計,對于我而言,中文版,書中很多地方一筆帶過,所以用自己所理解的,嘗試細致解讀下。內(nèi)存回收在談內(nèi)存泄漏之前,首先,先了解下的內(nèi)存回收機制。

內(nèi)存回收 && 內(nèi)存泄漏

前言:最近在細讀Javascript高級程序設計,對于我而言,中文版,書中很多地方一筆帶過,所以用自己所理解的,嘗試細致解讀下。如有紕漏或錯誤,會非常感謝您的指出。文中絕大部分內(nèi)容引用自《JavaScript高級程序設計第三版》。

內(nèi)存回收

在談“內(nèi)存泄漏”之前,首先,先了解下JavaScript的內(nèi)存回收機制。

JavaScript具有內(nèi)存自動回收機制,也就是說,執(zhí)行環(huán)境會負責管理代碼執(zhí)行過程中使用的內(nèi)存。

而在C和C++之類的語言中,開發(fā)人員的一項基本任務就是手動跟蹤內(nèi)存的使用情況,這是造成許多問題的根源。

在編寫JavaScript程序時,開發(fā)人員不用再關(guān)心內(nèi)存使用問題,所需內(nèi)存的分配以及所用內(nèi)存的回收,完全實現(xiàn)了自動管理。

這種內(nèi)存回收機制的原理,其實很簡單。即:找出那些不再繼續(xù)使用的變量,然后釋放其占用的內(nèi)存。

內(nèi)存回收器會按照固定的時間間隔(或代碼執(zhí)行中預定的收集時間), 周期性地執(zhí)行這一操作。

回顧下, 函數(shù)中局部變量的正常生命周期。

局部變量只在函數(shù)執(zhí)行的過程中存在。而在這個過程中,會為局部變量在棧(或堆)內(nèi)存上分配相應的空間,以便存儲它們的值。

然后在函數(shù)中使用這些變量,直至函數(shù)執(zhí)行結(jié)束。

此時,局部變量就沒有存在的必要了,因此可以釋放它們的內(nèi)存以供將來使用。

在這種情況下,很容易判斷變量是否還有存在的必要了。

但是,實際情況卻很復雜,不是那么容易得出結(jié)論的。

內(nèi)存回收器必須跟蹤哪個變量有用,哪個變量沒用。

對于不再有用的變量打上標記,以備將來回收其占用的內(nèi)存。

用于標識無用變量的策略可能會因?qū)崿F(xiàn)而異,具體到瀏覽器中的實現(xiàn),則通常有兩種策略。

標記清除策略

JavaScript中最常用的內(nèi)存回收方式是標記清除(mark-and-sweep)。當變量進入環(huán)境(例如:在函數(shù)中聲明一個變量)時,就將這個變量標記為“進入環(huán)境”。

從邏輯上講,永遠不能釋放進入執(zhí)行環(huán)境的變量所占用的內(nèi)存,只要執(zhí)行流進入相應的環(huán)境,就可能會用到它們。

而當變量離開環(huán)境時,則將其標記為“離開環(huán)境”。

可以使用任何方式來標記變量。比如,可以通過翻轉(zhuǎn)某個特殊的位來記錄一個變量何時進入環(huán)境,或者使用一個“進入環(huán)境的”變量列表以及一個“離開環(huán)境的”變量列表,來跟蹤變量。說到底,如何標記變量其實不重要,關(guān)鍵在于采取什么策略。

內(nèi)存回收器在運行的時候會給存儲在內(nèi)存中的所有變量都加上標記(當然,可以使用任何標記方式)。
然后,它會去掉環(huán)境中正在引用的變量的標記(標記意味著要被回收)。
而后,再被加上標記的變量被視為準備回收,因為環(huán)境中的變量已經(jīng)無法訪問到這些變量了。
最后,內(nèi)存回收器完成內(nèi)存回收工作,銷毀那些帶標記的值,并回收它們所占用的內(nèi)存空間。

IE、Firefox、Opera、Chrome和Safari的JavaScript內(nèi)存回收,使用的都是標記清除氏的內(nèi)存回收策略,只不過內(nèi)存回收的時間間隔互有不同。

引用計數(shù)

這一部分可稍作了解

另一種不太常見的內(nèi)存回收策略,引用計數(shù)(reference counting)。

引用計數(shù)的含義是跟蹤記錄每個值被引用的次數(shù)。

當聲明了一個變量并將一個引用類型值賦給該變量時,則這個值的引用次數(shù)就是1。
如果同一個值又被賦給另外一個變量,則該值的引用次數(shù)加1。
相反,如果包含對這個值引用的變量又取得了另外一個值,則這個值的引用次數(shù)減1。
當這個值的引用次數(shù)變成0時,則說明沒有辦法再訪問這個值了,因而可以將其占用的內(nèi)存空間回收。
當內(nèi)存回收器再次運行時,它就會釋放那些引用次數(shù)為零的值所占用的內(nèi)存。

Netscape Navigator 3.0是最早使用引用計數(shù)策略的瀏覽器,但很快它就遇到一個嚴重的問題:循環(huán)引用。

循環(huán)引用指的是對象A中包含一個指向?qū)ο驜的指針,而對象B中也包含一個指向?qū)ο驛的引用。

function referenceCountingProblem () {
    //調(diào)用函數(shù)并執(zhí)行的話
    var objectA = new Object(); // objectA引用值的reference counting 為1
    var objectB = new Object(); // objectB引用值的reference counting 為1 

    objectA.otherObject = objectB; // 現(xiàn)在objectB引用值的reference counting為2
    objectB.anotherObject = objectA; // 現(xiàn)在objectA引用值的reference counting為2

}

在這個例子中,objectA和objectB通過各自的屬性相互引用;
這兩個對象的引用次數(shù)都是2。

在采用標記清除策略的實現(xiàn)中,由于函數(shù)執(zhí)行之后,這兩個對象都離開了作用域,因此這種相互引用不是個問題。

但在采用引用計數(shù)策略的實現(xiàn)中,當函數(shù)執(zhí)行完畢后,objectA和objectB還將繼續(xù)存在,因為它們的引用次數(shù)永遠不是0。
假如這個函數(shù)被重復多次調(diào)用,就會導致大量內(nèi)存得不到回收。 為此,Netscape在Navigator4.0中放棄了引用計數(shù)策略,
轉(zhuǎn)而采用標記清除(mark-and-sweep)來實現(xiàn)其內(nèi)存回收機制。

可是,引用計數(shù)導致的麻煩并為就此終結(jié)。

IE中有一部分對象并不是原生JavaScript對象。 例如,BOM和DOM中的對象就是使用C++以COM對象(Component Object Model,組件對象模型)的形式實現(xiàn)的,而COM對象的內(nèi)存回收機制采用的就是引用計數(shù)策略。

即使IE的JavaScript引擎是使用標記清除策略來實現(xiàn)的,但JavaScript訪問的COM對象依然是基于引用計數(shù)策略的。
只要在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意味著切斷變量與它此前引用的值之間的連接。當內(nèi)存回收器再次運行時,就會刪除這些值并回收它們占用的內(nèi)存。

為了解決上述問題,IE9把BOM和DOM對象都轉(zhuǎn)換成真正的JavaScript對象。
這樣,就避免了兩種內(nèi)存回收算法并存導致的問題,也消除了常見的內(nèi)存的泄漏問題。

內(nèi)存泄漏

由于IE9之前的版本對JScript對象和COM對象使用不同的內(nèi)存回收算法(策略)。

因此,閉包在IE的這些版本中會導致一些特殊的問題,具體來說,如果閉包的作用域鏈中保存著一個HTML元素,那么就意味著該元素無法被銷毀。

function handler() {
    var element = document.getElementById("someElement");
    element.onclick = function() {
        alert(element.id);
    }
}

以上代碼創(chuàng)建了一個座位element元素事件處理程序的閉包,而這個閉包則又創(chuàng)建了一個循環(huán)引用。
由于匿名函數(shù)保存了一個對hander()的活動對象的引用,因此就會導致無法減少element的引用數(shù)。
只要匿名函數(shù)存在,element的引用數(shù)至少也是1,因此,它所占用的內(nèi)存就永遠不會被回收。

不過,這個問題可通過稍微改寫一下代碼來解決。

function handler(){
    var element = document.getElementById("someElement");
    var id = element.id;

    element.onclick =  function(){
        console.log(id);
    };

    element = null;
}

在范例代碼中,通過把element.id的一個副本保存在一個變量中,并且在閉包中引用該變量消除循環(huán)引用。
但僅僅做到這一步,還是不能解決內(nèi)存泄漏的問題。
必須記住:閉包會引起包含函數(shù)的整個活動對象,而其中包含著element。
即使閉包不直接引用element,包含函數(shù)的活動對象中仍然會保存一個引用。
因此,有必要把element變量設置為null。
這樣就能夠解除對DOM對象的引用,順利地減少其引用數(shù),確保正?;厥掌湔加玫膬?nèi)存。

關(guān)于這里的闡述,我有不同的看法。 既然閉包引用這個變量,說明這個變量,是我們需要用到的,某種意義上說,這不是“內(nèi)存泄漏”??!

內(nèi)存回收導致的性能問題(IE)

此部分也可稍作了解,當然知道這些歷史,也會更加明白為啥都使用標記清除策略

內(nèi)存回收器是周期性運行的,如果為變量分配的內(nèi)存數(shù)量很客觀,那么回收工作量也是很大的。

在這種情況下,確定內(nèi)存回收的時間間隔是一個非常重要的問題。

說到內(nèi)存回收器多長時間運行一次,不禁讓人聯(lián)想到IE因此而聲名狼藉的性能問題。

IE的內(nèi)存回收器是根據(jù)內(nèi)存分配量運行的,具體一點說就是256變量||4096個對象(或數(shù)組)字面量 和數(shù)組元素(slot)|| 64KB的字符竄。

達到上述任何一個臨界值,內(nèi)存回收器就會運行。

這種實現(xiàn)方式的問題在于,一個腳本中本來就包含那么多變量,那么該腳本很可能會在其生命周期中一直保有那么多的變量。

而這樣一來,內(nèi)存回收器,就不得不頻繁的運行。 就引發(fā)了嚴重的性能問題。 促使IE7重寫了其內(nèi)存回收策略。

到IE7,其JavaScript引擎的內(nèi)存回收的實現(xiàn)改變了方式:觸發(fā)內(nèi)存回收的變量分配、字面量或數(shù)組元素的臨界值被調(diào)整為動態(tài)修正。

IE7中的各項臨界值在初始時與IE6相等。如果內(nèi)存回收過程中,回收的內(nèi)存分配量低于15%,則變量、字面量或數(shù)組元素的臨界值就會加倍。
這也說明,絕大多數(shù)變量是被引用著的,內(nèi)存回收的臨界值太低,需要往上調(diào)。

如果內(nèi)存回收了85%的內(nèi)尺寸分配量,則將各種臨界值重置回默認值。

這一看似簡單的調(diào)整,極大地提升了IE在運行包含大量JavaScript的頁面時的性能。

事實上,在有的游覽器紅可以觸發(fā)內(nèi)存回收,但是不建議這么做。在IE中,調(diào)用window.CollectGarbage()方法會立即執(zhí)行內(nèi)存回收。在Opera7及更高版本中,調(diào)用window.opera.collect()也會啟動內(nèi)存回收。

管理內(nèi)存

使用具備內(nèi)存回收機制的語言編寫程序,開發(fā)人員一般不必擔心內(nèi)存管理的問題。

但是,JavaScript在進行內(nèi)存管理及內(nèi)存回收面臨的問題還是有點與眾不同。

其中,最主要的一個問題,就是分配給Web瀏覽器的可用內(nèi)存數(shù)量通常比分配給桌面應用程序的少。

這樣做的目的是處于安全方面的考慮, 目的是防止運行JavaScript的網(wǎng)頁耗盡全部系統(tǒng)內(nèi)存而導致系統(tǒng)奔潰。

內(nèi)存限制問題不僅會影響給變量分配內(nèi)存,同時還會影響調(diào)用棧以及在一個線程中能夠同時執(zhí)行的語句數(shù)量。

因此,確保占用最少的內(nèi)存可以讓頁面獲得更好的性能。優(yōu)化內(nèi)存占用的最佳方式,就是為執(zhí)行中的代碼只保存必要的數(shù)據(jù)。

一旦數(shù)據(jù)不再有用,最好通過將其值設置為null來釋放其引用——這個做法叫做接觸引用(dereferencing)。

這一做法適用于大多數(shù)全局變量和全局對象的屬性。

局部變量會在它們離開執(zhí)行環(huán)境時自動被解除引用。

function createPerson(name) {
    var localPerson = new object();
    localPerson.name = name;
    return localPersonl;
}

var globalPerson = createPerson("Shaw");

//手工解除globalPerson的引用

globalPerson = null;

變量globalPerson取得了createPerson()函數(shù)返回的值。
在createPerson()函數(shù)內(nèi)部,我們創(chuàng)建了一個對象并將其賦給局部變量localPerson,然后又為該對象添加了一個名為name的屬性。
最后,當調(diào)用這個函數(shù)時,localPerson以函數(shù)值的形式返回并賦給全局變量globalPerson。
由于localPerson在createPerson()函數(shù)執(zhí)行完畢后就離開了其執(zhí)行環(huán)境,因此,無需我們顯式地為它解除引用。

但是對于全局變量globalPerson而言,則需要我們在不使用它的時候手工為它解除引用,這也是上面例子中,最后一行代碼的意義。

解除一個值的引用并不意味著自動回收該值所占用的內(nèi)存。

解除引用的真正作用是讓值脫離執(zhí)行環(huán)境,以便內(nèi)存回收器下次運行時將其回收。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/98819.html

相關(guān)文章

  • 詳細解說JavaScript內(nèi)存管理和GC算法

      JavaScript在創(chuàng)建變量(數(shù)組、字符串、對象等)是自動進行了分配內(nèi)存,而且當它沒有被使用的狀態(tài)下,會自動的釋放分配的內(nèi)容;其實這樣基層語言,如C語言,他們提供了內(nèi)存管理的接口,比如malloc()用于分配所需的內(nèi)存空間、free()釋放之前所分配的內(nèi)存空間?! ♂尫艃?nèi)存的過程稱為垃圾回收,例如avaScript這類高級語言可以提供了內(nèi)存自動分配和自動回收,其實這個自動儲存不會占用太多空間...

    3403771864 評論0 收藏0
  • 溫故js系列(14)-閉包&垃圾回收&內(nèi)存泄露&閉包應用&作用域鏈&

    摘要:該對象包含了函數(shù)的所有局部變量命名參數(shù)參數(shù)集合以及,然后此對象會被推入作用域鏈的前端。如果整個作用域鏈上都無法找到,則返回。此時的作用域鏈包含了兩個對象的活動對象和對象。 前端學習:教程&開發(fā)模塊化/規(guī)范化/工程化/優(yōu)化&工具/調(diào)試&值得關(guān)注的博客/Git&面試-前端資源匯總 歡迎提issues斧正:閉包 JavaScript-閉包 閉包(closure)是一個讓人又愛又恨的somet...

    Amio 評論0 收藏0
  • Android&Java面試題大全—金九銀十面試必備

    摘要:需要校驗字節(jié)信息是否符合規(guī)范,避免惡意信息和不規(guī)范數(shù)據(jù)危害運行安全。具有相同哈希值的鍵值對會組成鏈表。通過在協(xié)議下添加了一層協(xié)議對數(shù)據(jù)進行加密從而保證了安全。常見的非對稱加密包括等。 類加載過程 Java 中類加載分為 3 個步驟:加載、鏈接、初始化。 加載。 加載是將字節(jié)碼數(shù)據(jù)從不同的數(shù)據(jù)源讀取到JVM內(nèi)存,并映射為 JVM 認可的數(shù)據(jù)結(jié)構(gòu),也就是 Class 對象的過程。數(shù)據(jù)源可...

    renweihub 評論0 收藏0
  • Deep in JS - 收藏集 - 掘金

    摘要:今天同學去面試,做了兩道面試題全部做錯了,發(fā)過來給道典型的面試題前端掘金在界中,開發(fā)人員的需求量一直居高不下。 排序算法 -- JavaScript 標準參考教程(alpha) - 前端 - 掘金來自《JavaScript 標準參考教程(alpha)》,by 阮一峰 目錄 冒泡排序 簡介 算法實現(xiàn) 選擇排序 簡介 算法實現(xiàn) ... 圖例詳解那道 setTimeout 與循環(huán)閉包的經(jīng)典面...

    enali 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<