偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

瀏覽器垃圾回收機(jī)制與 Vue 項(xiàng)目?jī)?nèi)存泄漏場(chǎng)景分析

開(kāi)發(fā) 項(xiàng)目管理
瀏覽器的 Javascript 具有自動(dòng)垃圾回收機(jī)制(GC:Garbage Collecation),也就是說(shuō),執(zhí)行環(huán)境會(huì)負(fù)責(zé)管理代碼執(zhí)行過(guò)程中使用的內(nèi)存。

[[276667]]

 1. 介紹

瀏覽器的 Javascript 具有自動(dòng)垃圾回收機(jī)制(GC:Garbage Collecation),也就是說(shuō),執(zhí)行環(huán)境會(huì)負(fù)責(zé)管理代碼執(zhí)行過(guò)程中使用的內(nèi)存。其原理是:垃圾收集器會(huì)定期(周期性)找出那些不在繼續(xù)使用的變量,然后釋放其內(nèi)存。但是這個(gè)過(guò)程不是實(shí)時(shí)的,因?yàn)槠溟_(kāi)銷比較大并且 GC 時(shí)停止響應(yīng)其他操作,所以垃圾回收器會(huì)按照固定的時(shí)間間隔周期性的執(zhí)行。

不再使用的變量也就是生命周期結(jié)束的變量,當(dāng)然只可能是局部變量,全局變量的生命周期直至瀏覽器卸載頁(yè)面才會(huì)結(jié)束。局部變量只在函數(shù)的執(zhí)行過(guò)程中存在,而在這個(gè)過(guò)程中會(huì)為局部變量在?;蚨焉戏峙湎鄳?yīng)的空間,以存儲(chǔ)它們的值,然后在函數(shù)中使用這些變量,直至函數(shù)結(jié)束,而閉包中由于內(nèi)部函數(shù)的原因,外部函數(shù)并不能算是結(jié)束。

還是上代碼說(shuō)明吧:

  1. function 
  2.  fn1() { 
  3.  var obj = {name'hanzichi', age: 10}; 
  4. function fn2() { 
  5.  var obj = {name:'hanzichi', age: 10}; 
  6.  return obj; 
  7. var a = fn1(); 
  8. var b = fn2(); 

我們來(lái)看代碼是如何執(zhí)行的。首先聲明了兩個(gè)函數(shù),分別叫做 fn1 和 fn2,當(dāng) fn1 被調(diào)用時(shí),進(jìn)入 fn1 的環(huán)境,會(huì)開(kāi)辟一塊內(nèi)存存放對(duì)象 {name:'hanzichi',age:10},而當(dāng)調(diào)用結(jié)束后,出了 fn1 的環(huán)境,那么該塊內(nèi)存會(huì)被 JS 引擎中的垃圾回收器自動(dòng)釋放;在 fn2 被調(diào)用的過(guò)程中,返回的對(duì)象被全局變量 b 所指向,所以該塊內(nèi)存并不會(huì)被釋放。

這里問(wèn)題就出現(xiàn)了:到底哪個(gè)變量是沒(méi)有用的?所以垃圾收集器必須跟蹤到底哪個(gè)變量沒(méi)用,對(duì)于不再有用的變量打上標(biāo)記,以備將來(lái)收回其內(nèi)存。用于標(biāo)記的無(wú)用變量的策略可能因?qū)崿F(xiàn)而有所區(qū)別,通常情況下有兩種實(shí)現(xiàn)方式:標(biāo)記清除和引用計(jì)數(shù)。引用計(jì)數(shù)不太常用,標(biāo)記清除較為常用。

2. 標(biāo)記清除

js中最常用的垃圾回收方式就是標(biāo)記清除。當(dāng)變量進(jìn)入環(huán)境時(shí),例如,在函數(shù)中聲明一個(gè)變量,就將這個(gè)變量標(biāo)記為“進(jìn)入環(huán)境”。從邏輯上講,永遠(yuǎn)不能釋放進(jìn)入環(huán)境的變量所占用的內(nèi)存,因?yàn)橹灰獔?zhí)行流進(jìn)入相應(yīng)的環(huán)境,就可能會(huì)用到它們。而當(dāng)變量離開(kāi)環(huán)境時(shí),則將其標(biāo)記為“離開(kāi)環(huán)境”。

  1. function test(){ 
  2. var a = 10 ; // 被標(biāo)記 ,進(jìn)入環(huán)境 
  3. var b = 20 ; // 被標(biāo)記 ,進(jìn)入環(huán)境 
  4. test(); // 執(zhí)行完畢 之后 a、b又被標(biāo)離開(kāi)環(huán)境,被回收。 

垃圾回收器在運(yùn)行的時(shí)候會(huì)給存儲(chǔ)在內(nèi)存中的所有變量都加上標(biāo)記(當(dāng)然,可以使用任何標(biāo)記方式)。然后,它會(huì)去掉環(huán)境中的變量以及被環(huán)境中的變量引用的變量的標(biāo)記(閉包)。而在此之后再被加上標(biāo)記的變量將被視為準(zhǔn)備刪除的變量,原因是環(huán)境中的變量已經(jīng)無(wú)法訪問(wèn)到這些變量了。最后,垃圾回收器完成內(nèi)存清除工作,銷毀那些帶標(biāo)記的值并回收它們所占用的內(nèi)存空間。到目前為止,IE9+、Firefox、Opera、Chrome、Safari 的 JS 實(shí)現(xiàn)使用的都是標(biāo)記清除的垃圾回收策略或類似的策略,只不過(guò)垃圾收集的時(shí)間間隔互不相同。

3. 引用計(jì)數(shù)

引用計(jì)數(shù)的含義是跟蹤記錄每個(gè)值被引用的次數(shù)。當(dāng)聲明了一個(gè)變量并將一個(gè)引用類型值賦給該變量時(shí),則這個(gè)值的引用次數(shù)就是 1。如果同一個(gè)值又被賦給另一個(gè)變量,則該值的引用次數(shù)加 1。相反,如果包含對(duì)這個(gè)值引用的變量又取得了另外一個(gè)值,則這個(gè)值的引用次數(shù)減 1。當(dāng)這個(gè)值的引用次數(shù)變成 0 時(shí),則說(shuō)明沒(méi)有辦法再訪問(wèn)這個(gè)值了,因而就可以將其占用的內(nèi)存空間回收回來(lái)。這樣,當(dāng)垃圾回收器下次再運(yùn)行時(shí),它就會(huì)釋放那些引用次數(shù)為 0 的值所占用的內(nèi)存。

  1. function test() { 
  2.  var a = {}; // a指向?qū)ο蟮囊么螖?shù)為1 
  3.  var b = a; // a指向?qū)ο蟮囊么螖?shù)加1,為2 
  4.  var c = a; // a指向?qū)ο蟮囊么螖?shù)再加1,為3 
  5.  var b = {}; // a指向?qū)ο蟮囊么螖?shù)減1,為2 

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

  1. function fn() { 
  2.  var a = {}; 
  3.  var b = {}; 
  4.  a.pro = b; 
  5.  b.pro = a; 
  6. fn(); 

以上代碼 a 和 b 的引用次數(shù)都是 2, fn 執(zhí)行完畢后,兩個(gè)對(duì)象都已經(jīng)離開(kāi)環(huán)境,在標(biāo)記清除方式下是沒(méi)有問(wèn)題的,但是在引用計(jì)數(shù)策略下,因?yàn)?a 和 b 的引用次數(shù)不為 0,所以不會(huì)被垃圾回收器回收內(nèi)存,如果 fn函數(shù)被大量調(diào)用,就會(huì)造成內(nèi)存泄露。在 IE7 與 IE8 上,內(nèi)存直線上升。

我們知道,IE 中有一部分對(duì)象并不是原生 JS 對(duì)象。例如,其內(nèi)存泄露 DOM 和 BOM 中的對(duì)象就是使用 C++ 以 COM 對(duì)象的形式實(shí)現(xiàn)的,而 COM 對(duì)象的垃圾回收機(jī)制采用的就是引用計(jì)數(shù)策略。因此,即使IE的js引擎采用標(biāo)記清除策略來(lái)實(shí)現(xiàn),但 JS 訪問(wèn)的COM對(duì)象依然是基于引用計(jì)數(shù)策略的。換句話說(shuō),只要在 IE 中涉及 COM 對(duì)象,就會(huì)存在循環(huán)引用的問(wèn)題。

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

這個(gè)例子在一個(gè) DOM 元素 element 與一個(gè)原生js對(duì)象 myObject 之間創(chuàng)建了循環(huán)引用。其中,變量 myObject 有一個(gè)屬性 e 指向 element 對(duì)象;而變量 element 也有一個(gè)屬性 o 回指 myObject。由于存在這個(gè)循環(huán)引用,即使例子中的 DOM 從頁(yè)面中移除,它也永遠(yuǎn)不會(huì)被回收。

舉個(gè)栗子:

瀏覽器垃圾回收機(jī)制與 Vue 項(xiàng)目?jī)?nèi)存泄漏場(chǎng)景分析
  • 黃色是指直接被 js變量所引用,在內(nèi)存里
  • 紅色是指間接被 js變量所引用,如上圖,refB 被 refA 間接引用,導(dǎo)致即使 refB 變量被清空,也是不會(huì)被回收的
  • 子元素 refB 由于 parentNode 的間接引用,只要它不被刪除,它所有的父元素(圖中紅色部分)都不會(huì)被刪除

另一個(gè)例子:

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

這段代碼看起來(lái)沒(méi)什么問(wèn)題,但是 obj 引用了 document.getElementById('element'),而 document.getElementById('element') 的 onclick 方法會(huì)引用外部環(huán)境中的變量,自然也包括 obj,是不是很隱蔽啊。(在比較新的瀏覽器中在移除Node的時(shí)候已經(jīng)會(huì)移除其上的event了,但是在老的瀏覽器,特別是 IE 上會(huì)有這個(gè) bug)

解決辦法:

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

  1. myObject.element = null
  2. element.o = null
  3.  
  4. window.onload=function outerFunction(){ 
  5.  var obj = document.getElementById("element"); 
  6.  obj.onclick=function innerFunction(){}; 
  7.  obj=null
  8. }; 

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

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

4. 內(nèi)存管理

4.1 什么時(shí)候觸發(fā)垃圾回收?

垃圾回收器周期性運(yùn)行,如果分配的內(nèi)存非常多,那么回收工作也會(huì)很艱巨,確定垃圾回收時(shí)間間隔就變成了一個(gè)值得思考的問(wèn)題。IE6的垃圾回收是根據(jù)內(nèi)存分配量運(yùn)行的,當(dāng)環(huán)境中存在256個(gè)變量、4096個(gè)對(duì)象、64k的字符串任意一種情況的時(shí)候就會(huì)觸發(fā)垃圾回收器工作,看起來(lái)很科學(xué),不用按一段時(shí)間就調(diào)用一次,有時(shí)候會(huì)沒(méi)必要,這樣按需調(diào)用不是很好嗎?但是如果環(huán)境中就是有這么多變量等一直存在,現(xiàn)在腳本如此復(fù)雜,很正常,那么結(jié)果就是垃圾回收器一直在工作,這樣瀏覽器就沒(méi)法兒玩兒了。

微軟在 IE7 中做了調(diào)整,觸發(fā)條件不再是固定的,而是動(dòng)態(tài)修改的,初始值和 IE6 相同,如果垃圾回收器回收的內(nèi)存分配量低于程序占用內(nèi)存的15%,說(shuō)明大部分內(nèi)存不可被回收,設(shè)的垃圾回收觸發(fā)條件過(guò)于敏感,這時(shí)候把臨街條件翻倍,如果回收的內(nèi)存高于 85%,說(shuō)明大部分內(nèi)存早就該清理了,這時(shí)候把觸發(fā)條件置回。這樣就使垃圾回收工作職能了很多

4.2 合理的GC方案

1. 基礎(chǔ)方案

Javascript 引擎基礎(chǔ)GC方案是(simple GC):mark and sweep(標(biāo)記清除),即:

遍歷所有可訪問(wèn)的對(duì)象。

回收已不可訪問(wèn)的對(duì)象。

2. GC的缺陷

和其他語(yǔ)言一樣,JS 的 GC 策略也無(wú)法避免一個(gè)問(wèn)題:GC 時(shí),停止響應(yīng)其他操作,這是為了安全考慮。而 Javascript 的 GC 在 100ms 甚至以上,對(duì)一般的應(yīng)用還好,但對(duì)于 JS 游戲,動(dòng)畫對(duì)連貫性要求比較高的應(yīng)用,就麻煩了。這就是新引擎需要優(yōu)化的點(diǎn):避免GC造成的長(zhǎng)時(shí)間停止響應(yīng)。

3. GC優(yōu)化策略

David 大叔主要介紹了2個(gè)優(yōu)化方案,而這也是最主要的2個(gè)優(yōu)化方案了:

分代回收(Generation GC)

這個(gè)和Java回收策略思想是一致的,也是V8所主要采用的。目的是通過(guò)區(qū)分“臨時(shí)”與“持久”對(duì)象;多回收“臨時(shí)對(duì)象”區(qū)(young generation),少回收“持久對(duì)象”區(qū)(tenured generation),減少每次需遍歷的對(duì)象,從而減少每次GC的耗時(shí)。如圖:

瀏覽器垃圾回收機(jī)制與 Vue 項(xiàng)目?jī)?nèi)存泄漏場(chǎng)景分析

這里需要補(bǔ)充的是:對(duì)于 tenured generation 對(duì)象,有額外的開(kāi)銷:把它從 young generation 遷移到 tenured generation,另外,如果被引用了,那引用的指向也需要修改。這里主要內(nèi)容可以參考深入淺出Node中關(guān)于內(nèi)存的介紹,很詳細(xì)~

增量GC

這個(gè)方案的思想很簡(jiǎn)單,就是“每次處理一點(diǎn),下次再處理一點(diǎn),如此類推”。如圖:

瀏覽器垃圾回收機(jī)制與 Vue 項(xiàng)目?jī)?nèi)存泄漏場(chǎng)景分析

這種方案,雖然耗時(shí)短,但中斷較多,帶來(lái)了上下文切換頻繁的問(wèn)題。因?yàn)槊糠N方案都其適用場(chǎng)景和缺點(diǎn),因此在實(shí)際應(yīng)用中,會(huì)根據(jù)實(shí)際情況選擇方案。比如:低 (對(duì)象/s) 比率時(shí),中斷執(zhí)行GC的頻率,simple GC更低些;如果大量對(duì)象都是長(zhǎng)期“存活”,則分代處理優(yōu)勢(shì)也不大。

4. Vue 中的內(nèi)存泄漏問(wèn)題

JS 程序的內(nèi)存溢出后,會(huì)使某一段函數(shù)體永遠(yuǎn)失效(取決于當(dāng)時(shí)的 JS 代碼運(yùn)行到哪一個(gè)函數(shù)),通常表現(xiàn)為程序突然卡死或程序出現(xiàn)異常。

這時(shí)我們就要對(duì)該 JS 程序進(jìn)行內(nèi)存泄漏的排查,找出哪些對(duì)象所占用的內(nèi)存沒(méi)有釋放。這些對(duì)象通常都是開(kāi)發(fā)者以為釋放掉了,但事實(shí)上仍被某個(gè)閉包引用著,或者放在某個(gè)數(shù)組里面。

4.1 泄漏點(diǎn)

DOM/BOM 對(duì)象泄漏;

script 中存在對(duì) DOM/BOM 對(duì)象的引用導(dǎo)致;

JS 對(duì)象泄漏;

通常由閉包導(dǎo)致,比如事件處理回調(diào),導(dǎo)致 DOM 對(duì)象和腳本中對(duì)象雙向引用,這個(gè)是常見(jiàn)的泄漏原因;

4.2 代碼關(guān)注點(diǎn)

主要關(guān)注的就是各種事件綁定場(chǎng)景,比如:

DOM 中的 addEventLisner 函數(shù)及派生的事件監(jiān)聽(tīng),比如 Jquery 中的 on 函數(shù),Vue 組件實(shí)例的 $on 函數(shù);

其它 BOM 對(duì)象的事件監(jiān)聽(tīng), 比如 websocket 實(shí)例的 on 函數(shù);

避免不必要的函數(shù)引用;

如果使用 render 函數(shù),避免在 HTML 標(biāo)簽中綁定 DOM/BOM 事件;

4.3 如何處理

如果在 mounted/created 鉤子中使用 JS 綁定了 DOM/BOM 對(duì)象中的事件,需要在 beforeDestroy 中做對(duì)應(yīng)解綁處理;

如果在 mounted/created 鉤子中使用了第三方庫(kù)初始化,需要在 beforeDestroy 中做對(duì)應(yīng)銷毀處理(一般用不到,因?yàn)楹芏鄷r(shí)候都是直接全局 Vue.use);

如果組件中使用了 setInterval,需要在 beforeDestroy 中做對(duì)應(yīng)銷毀處理;

4.4 在 vue 組件中處理 addEventListener

調(diào)用 addEventListener 添加事件監(jiān)聽(tīng)后在 beforeDestroy 中調(diào)用 removeEventListener移除對(duì)應(yīng)的事件監(jiān)聽(tīng)。為了準(zhǔn)確移除監(jiān)聽(tīng),盡量不要使用匿名函數(shù)或者已有的函數(shù)的綁定來(lái)直接作為事件監(jiān)聽(tīng)函數(shù)。

  1. mounted() { 
  2.  const box = document.getElementById('time-line'
  3.  this.width = box.offsetWidth 
  4.  this.resizefun = () => { 
  5.  this.width = box.offsetWidth 
  6.  } 
  7.  window.addEventListener('resize', this.resizefun) 
  8.  }, 
  9.  beforeDestroy() { 
  10.  window.removeEventListener('resize', this.resizefun) 
  11.  this.resizefun = null 
  12.  } 

4.5 觀察者模式引起的內(nèi)存泄漏

在 spa 應(yīng)用中使用觀察者模式的時(shí)候如果給觀察者注冊(cè)了被觀察的方法,而沒(méi)有在離開(kāi)組件的時(shí)候及時(shí)移除,可能造成重復(fù)注冊(cè)而內(nèi)存泄漏;

舉個(gè)栗子:進(jìn)入組件的時(shí)候 ob.addListener("enter",_func),如果離開(kāi)組件 beforeDestroy 的時(shí)候沒(méi)有 ob.removeListener("enter",_func),就會(huì)導(dǎo)致內(nèi)存泄漏。

更詳細(xì)的栗子參考:德州撲克栗子

4.6 上下文綁定引起的內(nèi)存泄漏

有時(shí)候使用 bind/apply/call 上下文綁定方法的時(shí)候,會(huì)有內(nèi)存泄漏的隱患。

  1. var ClassA = function(name) { 
  2.  this.name = name 
  3.  this.func = null 
  4. var a = new ClassA("a"
  5. var b = new ClassA("b"
  6. b.func = bind(function() { 
  7.  console.log("I am " + this.name
  8. }, a) 
  9. b.func() // 輸出: I am a 
  10. a = null // 釋放a 
  11. //b = null; // 釋放b 
  12. //b.func = null; // 釋放b.func 
  13. function bind(func, self) { // 模擬上下文綁定 
  14.  return function() { 
  15.  return func.apply(self) 
  16.  } 

使用 chrome dev tool>memory>profiles 查看內(nèi)存中 ClassA 的實(shí)例數(shù),發(fā)現(xiàn)有兩個(gè)實(shí)例, a和 b。雖然 a 設(shè)置成 null 了,但是 b 的方法中 bind 的閉包上下文 self 綁定了 a,因此雖然 a釋放,但是 b/b.func沒(méi)有釋放,閉包的 self 一直存在并保持對(duì) a 的引用。

 

責(zé)任編輯:武曉燕 來(lái)源: 今日頭條
相關(guān)推薦

2009-12-09 17:28:34

PHP垃圾回收機(jī)制

2010-09-26 16:42:04

JVM內(nèi)存組成JVM垃圾回收

2023-02-28 07:56:07

V8內(nèi)存管理

2010-09-25 15:33:19

JVM垃圾回收

2017-08-17 15:40:08

大數(shù)據(jù)Python垃圾回收機(jī)制

2017-03-03 09:26:48

PHP垃圾回收機(jī)制

2009-06-23 14:15:00

Java垃圾回收

2021-09-26 05:06:46

JS垃圾內(nèi)存

2021-11-05 15:23:20

JVM回收算法

2010-09-25 15:26:12

JVM垃圾回收

2021-05-27 21:47:12

Python垃圾回收

2011-07-04 16:48:56

JAVA垃圾回收機(jī)制GC

2010-09-16 15:10:24

JVM垃圾回收機(jī)制

2015-06-04 09:38:39

Java垃圾回收機(jī)

2017-06-12 17:38:32

Python垃圾回收引用

2011-06-28 12:39:34

Java垃圾回收

2013-04-01 10:07:19

Java內(nèi)存回收機(jī)制

2021-12-07 08:01:33

Javascript 垃圾回收機(jī)制前端

2010-10-13 10:24:38

垃圾回收機(jī)制JVMJava

2011-07-04 13:12:04

JavaScript
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)