JS 的垃圾回收機制與內(nèi)存管理

垃圾回收機制

Javascript具有自動垃圾回收機制(GC:Garbage Collecation)缀蹄,也就是說魄咕,執(zhí)行環(huán)境會負責(zé)管理代碼執(zhí)行過程中使用的內(nèi)存弛槐。

原理:垃圾收集器會定期(周期性)找出那些不在繼續(xù)使用的變量振劳,然后釋放其內(nèi)存摇锋。

JavaScript垃圾回收的機制很簡單:找出不再使用的變量知牌,然后釋放掉其占用的內(nèi)存祈争,但是這個過程不是實時的,因為其開銷比較大角寸,所以垃圾回收器會按照固定的時間間隔周期性的執(zhí)行菩混。

不再使用的變量也就是生命周期結(jié)束的變量,當然只可能是局部變量扁藕,全局變量的生命周期直至瀏覽器卸載頁面才會結(jié)束沮峡。局部變量只在函數(shù)的執(zhí)行過程中存在,而在這個過程中會為局部變量在椧诟蹋或堆上分配相應(yīng)的空間邢疙,以存儲它們的值,然后在函數(shù)中使用這些變量望薄,直至函數(shù)結(jié)束疟游,而閉包中由于內(nèi)部函數(shù)的原因,外部函數(shù)并不能算是結(jié)束

function fn1() {
 var obj = {name: 'hanzichi', age: 10};
//變量已清除
}
 
function fn2() {
 var obj = {name:'hanzichi', age: 10};
 return obj;
}
 
var a = fn1();
var b = fn2();
//函數(shù)的返回值含有變量 并 被 全局變量b 引用

我們來看代碼是如何執(zhí)行的痕支。首先定義了兩個function颁虐,分別叫做fn1和fn2,當fn1被調(diào)用時采转,進入fn1的環(huán)境聪廉,會開辟一塊內(nèi)存存放對象{name: 'hanzichi', age: 10},而當調(diào)用結(jié)束后故慈,出了fn1的環(huán)境板熊,那么該塊內(nèi)存會被js引擎中的垃圾回收器自動釋放;在fn2被調(diào)用的過程中察绷,返回的對象被全局變量b所指向干签,所以該塊內(nèi)存并不會被釋放。

這里問題就出現(xiàn)了:到底哪個變量是沒有用的拆撼?所以垃圾收集器必須跟蹤到底哪個變量沒用容劳,對于不再有用的變量打上標記,以備將來收回其內(nèi)存闸度。用于標記的無用變量的策略可能因?qū)崿F(xiàn)而有所區(qū)別竭贩,通常情況下有兩種實現(xiàn)方式:標記清除和引用計數(shù)。引用計數(shù)不太常用莺禁,標記清除較為常用留量。

標記清除

js中最常用的垃圾回收方式就是標記清除。當變量進入環(huán)境時,例如楼熄,在函數(shù)中聲明一個變量忆绰,就將這個變量標記為“進入環(huán)境”。從邏輯上講可岂,永遠不能釋放進入環(huán)境的變量所占用的內(nèi)存错敢,因為只要執(zhí)行流進入相應(yīng)的環(huán)境,就可能會用到它們缕粹。而當變量離開環(huán)境時稚茅,則將其標記為“離開環(huán)境”。

function test(){
 var a = 10 ; //被標記 平斩,進入環(huán)境 
 var b = 20 ; //被標記 峰锁,進入環(huán)境
}
test(); //執(zhí)行完畢 之后 a、b又被標離開環(huán)境双戳,被回收虹蒋。

垃圾回收器在運行的時候會給存儲在內(nèi)存中的所有變量都加上標記(當然,可以使用任何標記方式)飒货。然后魄衅,它會去掉環(huán)境中的變量以及被環(huán)境中的變量引用的變量的標記(閉包)。而在此之后再被加上標記的變量將被視為準備刪除的變量,原因是環(huán)境中的變量已經(jīng)無法訪問到這些變量了。最后癣丧,垃圾回收器完成內(nèi)存清除工作,銷毀那些帶標記的值并回收它們所占用的內(nèi)存空間哲银。

到目前為止,IE呻惕、Firefox荆责、Opera、Chrome亚脆、Safari的js實現(xiàn)使用的都是標記清除的垃圾回收策略或類似的策略做院,只不過垃圾收集的時間間隔互不相同。

引用計數(shù)

引用計數(shù)的含義是跟蹤記錄每個值被引用的次數(shù)濒持。當聲明了一個變量并將一個引用類型值賦給該變量時键耕,則這個值的引用次數(shù)就是1。如果同一個值又被賦給另一個變量柑营,則該值的引用次數(shù)加1屈雄。相反,如果包含對這個值引用的變量又取得了另外一個值官套,則這個值的引用次數(shù)減1酒奶。當這個值的引用次數(shù)變成0時蓖议,則說明沒有辦法再訪問這個值了,因而就可以將其占用的內(nèi)存空間回收回來讥蟆。這樣,當垃圾回收器下次再運行時纺阔,它就會釋放那些引用次數(shù)為0的值所占用的內(nèi)存瘸彤。

function test(){
 var a = {} ; //a的引用次數(shù)為0 
 var b = a ; //a的引用次數(shù)加1,為1 
 var c =a; //a的引用次數(shù)再加1笛钝,為2
 var b ={}; //a的引用次數(shù)減1质况,為1
}

Netscape Navigator3是最早使用引用計數(shù)策略的瀏覽器,但很快它就遇到一個嚴重的問題:循環(huán)引用玻靡。循環(huán)引用指的是對象A中包含一個指向?qū)ο驜的指針结榄,而對象B中也包含一個指向?qū)ο驛的引用。

function fn() {
 var a = {};
 var b = {};
 a.pro = b;
 b.pro = a;
}
 
fn();

以上代碼a和b的引用次數(shù)都是2囤捻,fn()執(zhí)行完畢后臼朗,兩個對象都已經(jīng)離開環(huán)境,在標記清除方式下是沒有問題的蝎土,但是在引用計數(shù)策略下视哑,因為a和b的引用次數(shù)不為0,所以不會被垃圾回收器回收內(nèi)存誊涯,如果fn函數(shù)被大量調(diào)用挡毅,就會造成內(nèi)存泄露。在IE7與IE8上暴构,內(nèi)存直線上升跪呈。

我們知道,IE中有一部分對象并不是原生js對象取逾。例如耗绿,其內(nèi)存泄露DOM和BOM中的對象就是使用C++以COM對象的形式實現(xiàn)的,而COM對象的垃圾回收機制采用的就是引用計數(shù)策略砾隅。因此缭乘,即使IE的js引擎采用標記清除策略來實現(xiàn),但js訪問的COM對象依然是基于引用計數(shù)策略的琉用。換句話說堕绩,只要在IE中涉及COM對象,就會存在循環(huán)引用的問題邑时。

var element = document.getElementById("some_element");
var myObject = new Object();
myObject.e = element;
element.o = myObject;

這個例子在一個DOM元素(element)與一個原生js對象(myObject)之間創(chuàng)建了循環(huán)引用奴紧。其中,變量myObject有一個名為element的屬性指向element對象晶丘;而變量element也有一個屬性名為o回指myObject黍氮。由于存在這個循環(huán)引用唐含,即使例子中的DOM從頁面中移除,它也永遠不會被回收沫浆。

window.onload=function outerFunction(){
 var obj = document.getElementById("element");
 obj.onclick=function innerFunction(){};
};

這段代碼看起來沒什么問題捷枯,但是obj引用了document.getElementById(“element”),而document.getElementById(“element”)的onclick方法會引用外部環(huán)境中的變量专执,自然也包括obj淮捆,是不是很隱蔽啊。

  • 解決辦法

最簡單的方式就是自己手工解除循環(huán)引用本股,比如剛才的函數(shù)可以這樣

window.onload=function outerFunction(){
 var obj = document.getElementById("element");
 obj.onclick=function innerFunction(){};
 obj=null;
};

將變量設(shè)置為null意味著切斷變量與它此前引用的值之間的連接攀痊。當垃圾回收器下次運行時,就會刪除這些值并回收它們占用的內(nèi)存拄显。

要注意的是苟径,IE9+并不存在循環(huán)引用導(dǎo)致Dom內(nèi)存泄露問題,可能是微軟做了優(yōu)化躬审,或者Dom的回收方式已經(jīng)改變

內(nèi)存管理

  • 什么時候觸發(fā)垃圾回收棘街?

垃圾回收器周期性運行,如果分配的內(nèi)存非常多承边,那么回收工作也會很艱巨蹬碧,確定垃圾回收時間間隔就變成了一個值得思考的問題。IE6的垃圾回收是根據(jù)內(nèi)存分配量運行的炒刁,當環(huán)境中存在256個變量恩沽、4096個對象、64k的字符串任意一種情況的時候就會觸發(fā)垃圾回收器工作翔始,看起來很科學(xué)罗心,不用按一段時間就調(diào)用一次,有時候會沒必要城瞎,這樣按需調(diào)用不是很好嗎渤闷?但是如果環(huán)境中就是有這么多變量等一直存在,現(xiàn)在腳本如此復(fù)雜脖镀,很正常飒箭,那么結(jié)果就是垃圾回收器一直在工作,這樣瀏覽器就沒法兒玩兒了蜒灰。

微軟在IE7中做了調(diào)整弦蹂,觸發(fā)條件不再是固定的,而是動態(tài)修改的强窖,初始值和IE6相同凸椿,如果垃圾回收器回收的內(nèi)存分配量低于程序占用內(nèi)存的15%,說明大部分內(nèi)存不可被回收翅溺,設(shè)的垃圾回收觸發(fā)條件過于敏感脑漫,這時候把臨界條件翻倍髓抑,如果回收的內(nèi)存高于85%,說明大部分內(nèi)存早就該清理了优幸,這時候把觸發(fā)條件置回吨拍。這樣就使垃圾回收工作智能了很多

  • 合理的GC方案

1、 Javascript引擎基礎(chǔ)GC方案是(simple GC):mark and sweep
(標記清除)即:
(1)遍歷所有可訪問的對象网杆。
(2)回收已不可訪問的對象羹饰。

2、GC的缺陷
和其他語言一樣跛璧,javascript的GC策略也無法避免一個問題:GC時,停止響應(yīng)其他操作新啼,這是為了安全考慮追城。而Javascript的GC在100ms甚至以上,對一般的應(yīng)用還好燥撞,但對于JS游戲座柱,動畫對連貫性要求比較高的應(yīng)用,就麻煩了物舒。這就是新引擎需要優(yōu)化的點:避免GC造成的長時間停止響應(yīng)色洞。

3、GC優(yōu)化策略
(1) 分代回收(Generation GC)
這個和Java回收策略思想是一致的冠胯。目的是通過區(qū)分“臨時”與“持久”對象火诸;多回收“臨時對象”區(qū),少回收“持久對象”區(qū)荠察,減少每次需遍歷的對象置蜀,從而減少每次GC的耗時
(2) 增量GC
這個方案的思想很簡單,就是“每次處理一點悉盆,下次再處理一點盯荤,如此類推”

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市焕盟,隨后出現(xiàn)的幾起案子秋秤,更是在濱河造成了極大的恐慌,老刑警劉巖脚翘,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灼卢,死亡現(xiàn)場離奇詭異,居然都是意外死亡来农,警方通過查閱死者的電腦和手機芥玉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來备图,“玉大人灿巧,你說我怎么就攤上這事赶袄。” “怎么了抠藕?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵饿肺,是天一觀的道長。 經(jīng)常有香客問我盾似,道長敬辣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任零院,我火速辦了婚禮溉跃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘告抄。我一直安慰自己撰茎,他們只是感情好,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布打洼。 她就那樣靜靜地躺著龄糊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪募疮。 梳的紋絲不亂的頭發(fā)上炫惩,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機與錄音阿浓,去河邊找鬼他嚷。 笑死,一個胖子當著我的面吹牛芭毙,可吹牛的內(nèi)容都是我干的爸舒。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼稿蹲,長吁一口氣:“原來是場噩夢啊……” “哼扭勉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起苛聘,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤涂炎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后设哗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唱捣,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年网梢,在試婚紗的時候發(fā)現(xiàn)自己被綠了震缭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡战虏,死狀恐怖拣宰,靈堂內(nèi)的尸體忽然破棺而出党涕,到底是詐尸還是另有隱情,我是刑警寧澤巡社,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布膛堤,位于F島的核電站,受9級特大地震影響晌该,放射性物質(zhì)發(fā)生泄漏肥荔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一朝群、第九天 我趴在偏房一處隱蔽的房頂上張望燕耿。 院中可真熱鬧,春花似錦姜胖、人聲如沸誉帅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽堵第。三九已至吧凉,卻和暖如春隧出,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阀捅。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工胀瞪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饲鄙。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓凄诞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親忍级。 傳聞我的和親對象是個殘疾皇子帆谍,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容