js內存回收

概念:html

兩種類型的泄露:node

週期性的內存增加致使的泄露,以及偶現的內存泄露。顯而易見,週期性的內存泄露很容易發現;偶現的泄露比較棘手,通常容易被忽視,偶爾發生一次可能被認爲是優化問題,週期性發生的則被認爲是必須解決的 bug。算法

js中堆和棧chrome

棧:stack - 存放原始值(簡單數據類型),連續的存儲空間。棧空間小,讀寫快。閉包

堆:heap - 存放引用值(new arry object...),散列的存儲空間。堆空間大,讀寫慢。dom

例如:當咱們用new實例化一個類的時候,這個new出來的對象就保存在heap裏面,而這個對象的引用則存儲在stack裏。程序經過stack裏的引用找到這個對象。例如var a = [1,2,3];,a是存儲在stack裏的引用,heap裏存儲着內容爲[1,2,3]的Array對象函數

js對象:post

本地對象:(object  array  function)優化

宿主對象:(dom bom)this

本地對象之間使用標記清除,不會形成內存泄漏。

本地和宿主對象之間使用引用計數,關聯至當下cocos egret 引擎。(循環引用,閉包)

總方針:在使用完畢後切斷引用鏈,解除事件綁定。

堆的內存釋放由一特定算法的垃圾收集器進行(GC):標記清除 引用計數 複製算法

本質:當一個對象無用的時候,即程序中無變量引用這個對象時,就會從內存中釋放掉這個變量。  

一、標記清除

function test(){
  var a = 10;//被標記進入環境  
}
test();//執行結束後被標記離開環境 被回收

 

二、引用計數

function test(){
  var a = {}; //a的引用次數爲0
  var b = a; //a的引用次數爲1
  var c = a;//a的引用次數爲2
  var b = {};  //a的引用次數減1 爲 1 
}

當a 爲零的時候,gc會將其回收銷燬。

注意:循環引用計數,相互引用將沒法使用引用計數回收。

function fn(){
  var a = {};
  var b ={};
  a.obj = b;
  b.obj = a;
}
fn();
var element = document.getElementById(" ...");

var myObj = new Object();

myObj.e = element;

element.o = myObj;

這例子Dom對象element和本地對象myObj之間循環引用 

簡單描述:

  三個對象 A 、B 、C

     若A的某一屬性引用着B,一樣C也被B的屬性引用着。若是將A清除,那麼B、C也被釋放。

     若這裏增長了C的某一屬性引用B對象,若是這是清除A,那麼B、C不會被釋放,由於B和C之間產生了循環引用。

 var a = {};
    a.pro = { a:100 };
    a.pro.pro = { b:100 };
    a = null ; 
    //這種狀況下,{a:100}和{b:100}就同時也被釋放了。
            
    var obj = {};
    obj.pro = { a : 100 };
    obj.pro.pro = { b : 200 };
    var two = obj.pro.pro;
    obj = null;    
    //這種狀況下 {b:200}不會被釋放掉,而{a:100}被釋放了。
 

 

三、內存泄漏常見的狀況

1、意外的全局變量

function leaks(){
   leak ="xxx"; leak成爲全局變量不會被回收  
}

說明:js中若是不用var聲明變量,該變量將被視爲window對象(全局對象)的屬性,也就是全局變量.

function foo() {
    this.variable = "...";
}

// 沒有對象調用foo, 也沒有給它綁定this, 因此this是window
foo();

方案:添加"use strict" 可避免。

2、閉包引發的內存泄漏

function bindEvent(){
  var  obj =document.createElement("xx");
  obj.click = function(){
   //....
  }
}

閉包能夠維持函數內的局部變量,使其得不到釋放。

方案:將事件定義在外部, obj.click = this.clickFunction;  function clickFunction(){...}或者將其對象的引用刪除obj.click = null;

window.onunload = function(){
        var one = document.getElementById( 'xx' );
        one.click = null;
    };

拓展:在cocos & egret中就能夠遍歷進行刪除管理事件

3、沒有清理dom元素引用

var element = {
  button: document.getElementById("button");
}
function shuff(){
  button.click();RemoveButton()
}
function RemoveButton(){ document.body.removeChild(document.getElementById("button")); }

雖然 removeChild 移除了button,但element裏還保留着對button的引用,則button還保留在內存裏面。

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

var data = {};
setInterval(function(){
   var node = document.getElementById("Node");
if(node){
node.innerHtml = JSON.stringify(data);
}
...},
1000)

若是id爲Node的元素從Dom中移除,該定時器仍會存在,同時回調函數對data的引用,定時器外的data也沒法釋放。

方案:清除定時器。若是有引用變量同時設爲null。

5、子元素存在引用引發的內存泄漏

code

  • 黃色是指直接被 js變量所引用,在內存裏
  • 紅色是指間接被 js變量所引用,如上圖,refB 被 refA 間接引用,致使即便 refB 變量被清空,也是不會被回收的
  • 子元素 refB 因爲 parentNode 的間接引用,只要它不被刪除,它全部的父元素(圖中紅色部分)都不會被刪除 

方案:純粹refA = null 無效,須要refA = null ; refB =null;

var select = document.querySelector;
var treeRef = select('#tree');

var leafRef = select('#leaf');   //在COM樹中leafRef是treeFre的一個子結點

select('body').removeChild(treeRef);//#tree不能被回收入,由於treeRef還在

方案:

treeRef = null;//tree還不能被回收,由於葉子結果leafRef還在
leafRef = null;//如今#tree能夠被釋放了

 使用chrome查看泄漏

Heap Profiling能夠記錄當前的堆內存(heap)快照,並生成對象的描述文件,該描述文件給出了當時JS運行所用到的全部對象,以及這些對象所佔用的內存大小、引用的層級關係等等。(使用快照會自動執行一次gc

列字段解釋:
Constructor -- 類名Distance -- 估計是對象到根的引用層級距離
Objects Count -- 給出了當前有多少個該類的對象
Shallow Size -- 對象所佔內存(不包含內部引用的其它對象所佔的內存)(單位:字節)
Retained Size -- 對象所佔總內存(包含內部引用的其它對象所佔的內存)(單位:字節)

這裏以cocos egret爲例:打開一個界面a拍下快照,屢次切換界面後回到界面a再次拍下快照。選中第二個快照,點選summary選中comparison,chrome將自動比較,此時看delta這一欄,點擊排序查看增量,結合construcor中查找你寫的對應類與增量,進行排查!

 

 

參考:

一個意想不到的Javascript內存泄漏

JavaScript 內存泄漏教程

JS內存泄漏排查方法-Chrome Profiles

談一談Javascript內存釋放那點事

相關文章
相關標籤/搜索