簡介
JS自帶一套內(nèi)存管理引擎淹真,負責創(chuàng)建對象讶迁、銷毀對象,以及垃圾回收核蘸。這期探討一下垃圾回收機制巍糯。垃圾回收機制主要是由一個叫垃圾收集器(garbage collector,簡稱GC)的后臺進程負責監(jiān)控客扎、清理對象祟峦,并及時回收空閑內(nèi)存。
可達性(Reachability)
GC的最主要職責是監(jiān)控數(shù)據(jù)的可達性(reachability)徙鱼;哪些數(shù)據(jù)是所謂的可達的呢宅楞?
-
所有顯示調(diào)用,被稱為
根
袱吆,包括- 全局對象
- 正被調(diào)用的函數(shù)的局部變量和參數(shù)
- 相關(guān)嵌套函數(shù)里的變量和參數(shù)
- 其他(引擎內(nèi)部調(diào)用的一些變量)
所有從根引用或引用鏈訪問的對象
舉個簡單的例子
let user = {
name: 'Onion'
}
這里全局變量user指向內(nèi)存里的對象{name: 'Onion'}
厌衙,我們稱其為引用。這時對象Onion(以下均以名字簡稱)是所謂可達的绞绒。
將user置為null后婶希,引用丟失,Onion對象就變成不可達了蓬衡。最終GC會將它從內(nèi)存中清除喻杈。
user = null
再舉一個復雜一點的例子:
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman,
}
}
let family = marry({
name: "Onion"
}, {
name: "Garlic"
});
如圖,現(xiàn)階段所有對象都是可達的狰晚。
現(xiàn)在我們刪除一些對Onion的引用
delete family.father
delete family.mother.husband
如圖筒饰,盡管Onion還有對Garlic的引用,但是它本身已不可達家肯,所以很快就會被GC發(fā)現(xiàn)并回收龄砰。
回收算法
最基本的垃圾回收算法被稱為標記清除法(mark-and-sweep)。有這么幾步:
-
GC標記所有
根
的變量mark root -
訪問所有變量的引用,并標記它們
mark reference -
標記所有引用鏈上的對象换棚,已標記的對象不再被訪問
mark reference chain -
最后刪除所有未被標記的對象(注:并非未被引用的對象式镐,如圖右)
sweep unreachable
現(xiàn)代的GC引擎自然比這個復雜得多,很多優(yōu)化手段早已被用到各大廠家中固蚤,比如V8的分代回收(Generational collection)娘汞、增量回收(Incremental collection)、空閑時回收(Idle-time collection)等等夕玩。不過你弦,這些手段已超出了本文的范疇,不再深入探討燎孟。
內(nèi)存泄漏
內(nèi)存泄漏指申請的內(nèi)存一直得不到釋放禽作,GC回收不了。一般在項目中就是揩页,你創(chuàng)建的對象一直保存在內(nèi)存中旷偿,可達但你把它的引用地址搞丟了結(jié)果沒法操作它,而GC又不會回收這塊內(nèi)存爆侣。內(nèi)存泄漏的危害就是堆積耗盡系統(tǒng)所有內(nèi)存萍程。
常見的有這么幾種泄漏方式:
-
意外的全局變量
function foo() { bar = "等價于創(chuàng)建global變量window.bar"; }
-
忘記清空計時器
let someResource = {...}; setInterval(function cb() { let node = document.getElementById('Node'); if(node) { // 若不清空計時器,node和someResource將長期駐留內(nèi)存 node.innerHTML = JSON.stringify(someResource); } }, 1000);
-
閉包里的循環(huán)引用
function assignHandler(){ let element = $('id'); let id = elment.id; // 引用element變量id element.onclick = function(){ alert(id); // 引用assignHandler變量id }; }
-
其他
在ie等老舊瀏覽器里還有許多匪夷所思的內(nèi)存泄漏兔仰,比如自動類型裝箱轉(zhuǎn)換茫负,一些不經(jīng)意的DOM操作,甚至閉包本身就會泄漏乎赴;這類泄漏需要專人特別關(guān)注忍法,這里不再一一贅述了。
小結(jié)
今天簡單接受了一下Javascript的GC機制榕吼,由于功力有限我只能淺嘗則止缔赠。不過還是有幾點概念性的總結(jié):
- GC機制是自動完成的,但我們可以強制啟動它友题,或是關(guān)閉它嗤堰。
- 只要是可達的,對象就會常駐內(nèi)存度宦,所以需要特別注意內(nèi)存泄漏問題
- 引用與可達的是不一樣的踢匣,有些引用鏈可能根本無法在內(nèi)存中駐留