JS中的垃圾回收與內存泄漏總結


引用:https://blog.csdn.net/yingzizizizizizzz/article/details/77333996

https://www.cnblogs.com/zhuzhenwei918/p/7586240.html

垃圾回收機制

Js具有自動垃圾回收機制。垃圾收集器會按照固定的時間間隔周期性的執行。

標記清除

工作原理:是當變量進入環境時,將這個變量標記為“進入環境”。當變量離開環境時,則將其標記為“離開環境”。標記“離開環境”的就回收內存。

工作流程

  1. 垃圾回收器在運行的時候會給存儲在內存中的所有變量都加上標記。

  2. 去掉環境中的變量以及被環境中的變量引用的變量的標記(閉包)。

  3. 依然被標記的會被視為准備刪除的變量。

  4. 垃圾回收器完成內存清除工作,銷毀那些帶標記的值並回收他們所占用的內存空間。

引用計數

工作原理:跟蹤記錄每個值被引用的次數。

工作流程

  1. 聲明了一個變量並將一個引用類型的值賦值給這個變量,這個引用類型值的引用次數就是1。

  2. 同一個值又被賦值給另一個變量,這個引用類型值的引用次數加1.

  3. 當包含這個引用類型值的變量又被賦值成另一個值了,那么這個引用類型值的引用次數減1.

  4. 當引用次數變成0時,說明沒辦法訪問這個值了。

  5. 當垃圾收集器下一次運行時,它就會釋放引用次數是0的值所占的內存。

問題:循環引用

function problem() {
    var objA = new Object();
    var objB = new Object();

    objA.someOtherObject = objB;
    objB.anotherObject = objA;
}

 

imageobjA指向內存中的引用類型,而這個引用類型的一個值又指向了另一個引用類型,這樣,每個引用類型的引用次數都是2,且在引用類型之間形成了循環引用,這樣,即使problem()函數執行完畢,把后期不再使用的局部變量objA和objB釋放,但是因為引用類型的引用次數還是1,那么這兩個引用類型還是不能被釋放的,這就造成了內存泄露。

  • 循環引用實際上是在堆中的兩個引用類型之間的循環引用,如右邊的兩個箭頭就構成了一個循環。
  • 因為IE中的BOM、DOM的實現使用了COM,IE的JavaScript引擎使用的是標記清除的策略,但是JavaScript訪問的COM對象依然是基於引用計數的策略的。所以會存在循環引用的問題。
var element = document.getElementById("some_element");
var myObj =new Object();
myObj.element = element;
element.someObject = myObj;

 

這個例子中,一個DOM元素和一個原生JavaScript對象之間建立了循環引用。這樣,即使例子中的DOM從頁面中移除,內存也不會被回收

解決方法以手動切斷他們的循環引用

myObj.element = null;
element.someObject =null;

 

內存泄漏

使用了內存之后, 如果后面他們不會再被用到,但是還沒有及時釋放,這就叫做內存泄露(memory leak)。如果出現了內存泄露,那么有可能使得內存越來越大,而導致瀏覽器崩潰。

引起內存泄漏的情況

1. 意外的全局變量引起的內存泄漏。

  function foo(arg) {
    bar = "this is a hidden global variable"; // 沒有用var
  }


 function foo() {
    this.variable = "potential accidental global";
  }
  // 一般函數調用,this指向全局

 

原因:全局變量,不會被回收。

解決:使用嚴格模式避免,函數內使用var定義,塊內使用let、const。

2. 閉包引起的內存泄漏

原因:閉包可以維持函數內局部變量,使其得不到釋放。

解決:將事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,刪除對dom的引用。

3、 刪除元素造成的內存泄露。

 <div id="wrap">
    <span id="link">點擊</a>
  </div>
  <script>
    let wrap = document.getElementById('wrap');
        link = document.getElementById('link');
    function handleClick() {
      alert('clicked');
    }
    link.addEventListener('click', handleClick ,false);

    wrap.removeChild(link);
    document.body.appendChild(link);

 

即使link已經被移除了,然后我們通過appendChild添加到div平級的地方,然后點擊之后還是有事件發生的,說明這里元素被移除然后添加,事件還是可以用的。

但是,我們已經將之移除了,所以,后面就不需要了,但是span標簽還是被link變量所引用,這樣,就造成了內存泄露。

所以,我們可以在link被移除的時候,就清除這個引用,如下所示:

 <div id="wrap">
    <span id="link">點擊</a>
  </div>
  <script>
    let wrap = document.getElementById('wrap');
        link = document.getElementById('link');
    function handleClick() {
      alert('clicked');
    }
    link.addEventListener('click', handleClick ,false);

    wrap.removeChild(link);
    link = null;

 

原因:雖然DOM刪除了,但是對象中還存在對dom的引用

解決:手動刪除。

4. 被遺忘的定時器或者回調

原因:定時器中有dom的引用,即使dom刪除了,但是定時器還在,所以內存中還是有這個dom。 ** 解決**:手動刪除定時器和dom。

5. 子元素存在引用引起的內存泄

原因:div中的ul li , 得到這個div,會間接引用某個得到的li,那么此時因為div間接引用li,即使li被清空,也還是在內存中,並且只要li不被刪除,他的父元素都不會被刪除。 ** 解決**:手動刪除清空。

堆內存和棧內存

基本類型是:Undefined/Null/Boolean/Number/String,

基本類型的值存在內存中,被保存在棧內存中。從一個變量向另一個變量復制基本類型的值,會創建這個值的一個副本。

引用類型:object

引用類型的值是對象,保存在堆內存中。

  1. 包含引用類型值的變量實際上包含的並不是對象本身,而是一個指向該對象的指針。從一個變量向另一個變量復制引用類型的值,復制的其實是指針,因此兩個變量最終都指向同一個對象。

  2. js不允許直接訪問內存中的位置,也就是不能直接訪問操作對象的內存空間。在操作對象時,實際上是在操作對象的引用而不是實際的對象。

堆和棧的區別

一、堆棧空間分配區別:

1、棧(操作系統):由操作系統自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧;

2、堆(操作系統): 一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收,分配方式倒是類似於鏈表。

二、堆棧緩存方式區別:

1、棧使用的是一級緩存, 他們通常都是被調用時處於存儲空間中,調用完畢立即釋放;

2、堆是存放在二級緩存中,生命周期由虛擬機的垃圾回收算法來決定(並不是一旦成為孤兒對象就能被回收)。所以調用這些對象的速度要相對來得低一些。

三、堆棧數據結構區別:

堆(數據結構):堆可以被看成是一棵樹,如:堆排序;   棧(數據結構):一種先進后出的數據結構。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM