概念: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、子元素存在引用引發的內存泄漏
方案:純粹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)
這裏以cocos egret爲例:打開一個界面a拍下快照,屢次切換界面後回到界面a再次拍下快照。選中第二個快照,點選summary選中comparison,chrome將自動比較,此時看delta這一欄,點擊排序查看增量,結合construcor中查找你寫的對應類與增量,進行排查!
參考: