前端內存泄露問題


  最近發現項目有個bug,同時運行多個任務的時候,前端頁面報內存不足而導致頁面崩潰,這很明顯就是內存泄露了。我查看了一下,運行的過程中,因為運行時間很久,所以前端和后台約定了,用計時器setInternal定時去請求后台運行狀態,當運行狀態為完成時,前端會清除定時器。我預估是因為計時器而導致的內存泄露,在執行計時器代碼的時候,任務管理器的物理內存消耗一直在增加,這樣的話,要是多個任務同時在執行,而且任務執行較久的話,那樣物理內存就有可能會被占用完。后面我也復現了這個場景,果然是因為計時器的原因。

一、探究過程

疑惑1:會不會是因為JavaScript引擎是單線程,計時器會不斷把事件不斷放入事件隊列,而任務執行時間很長,所以才會導致事件隊列堆滿而導致內存泄露?

資料答疑:首先,糾正大家一個錯誤的理解,定時器並不是嚴格意義上會按個多少秒執行的,它可能會出現執行延遲或提前。在運行本行代碼的時候,將定時器的代碼添加到了事件隊列當中,而不是何時執行/運行代碼。此時需要等到當前“事件處理程序”運行之后再去執行定時器代碼。換句話說,就是,並非是設置的毫秒數后就執行定時器代碼,執行的時間是有可能提前/延后的。同時,JavaScript引擎設置了:僅當隊列中沒有該定時器的任何其他代碼實例時,才能夠將定時器代碼添加到隊列。所以不會出現導致連續運行多次的情況。

疑惑2:那到底是什么導致內存泄露呢?

資料答疑:內聯書寫setInterval時,由於匿名函數被定義於全局中,不能夠計時器的清除,因此很容易造成內存泄露。

二、實驗測試

  剛好我的計時器setInternal是用匿名函數寫的,很有可能是因為這個原因,所以我用了匿名函數和命名函數測試了一下。

1、匿名函數

代碼:

 1 mounted(){  2     let self = this;  3     self.setInternalId = setInterval(()=>{  4         let sum = 0,i=1;  5         while(i<100000000){  6             sum+=i++;  7             // sum=parseInt(sum/2)
 8  }  9         let now = new Date(); 10         console.log(sum,'秒數:',now.getSeconds()); 11     },2000); 12 }, 13 beforeDestroy(){ 14     let self = this
15  clearInterval(self.setInternalId); 16     console.log('消除定時器啦。。。。') 17 },

我發現物理內存是很緩慢增長的,所以要時間夠長才能會有明顯的區別,所以不能確定是匿名函數導致的。

 

2、命名函數

代碼:

 1 mounted(){  2     let self = this;  3     self.setInternalId = setInterval(this.getSum,2000);  4 },  5 beforeDestroy(){  6     let self = this
 7  clearInterval(self.setInternalId);  8     console.log('消除定時器啦。。。。')  9 }, 10 methods:{ 11  getSum(){ 12         let sum = 0,i=1; 13         while(i<100000000){ 14             sum+=i++; 15             // sum=parseInt(sum/2)
16  } 17         let now = new Date(); 18         console.log(sum,'秒數:',now.getSeconds()); 19  } 20 }

我持續觀察了半個多小時物理內存的變化,發現內存是時增長時減低,增長或降低的幅度都不會很大。

 

3、增加http請求

后面我在里面增加一個htttp請求后台數據,發現物理內存是一直上升的,上升的幅度明顯比匿名函數大,上升的速度也很快,所以可能是前端請求導致的內存泄露。同時為了校驗是setInternal還是http請求導致的內存泄露,我做了以下的代碼校驗:

 1 mounted(){  2     let self = this;  3     self.setInternalId2  = setInterval(()=>{  4         let now = new Date();  5         console.log('第一個:',now.getSeconds());  6         self.getAllTasks(1); // http請求
 7         self.setInternalId3 = setInterval(()=>{  8             let sum =0,i=1;  9             while(i<1000000){ 10                 sum+=i++; 11  } 12             let now = new Date(); 13             console.log('第二個:',now.getSeconds()); 14         },3000) 15     },1000) 16 }, 17 beforeDestroy(){ 18     let self = this; 19  clearInterval(self.setInternalId2); 20  clearInterval(self.setInternalId3); 21     console.log('消除計時器。。。。。') 22 },

發中間的setInternal計時器刪掉后,物理內存消耗還是持續快速的增長。后面查資料得知,瀏覽器對單窗口的http請求數量是有限制的,谷歌瀏覽器可以並發執行最多6個,所以會導致http請求阻塞,從而消耗內存

 

總結:這兩個測試並不能證明是匿名函數所引起的內存泄露,很有可能是不斷重復執行 ajax 引起的,但是我目前還查不到相關的資料證明,這個問題還有待考察。

 

三、常見的內存泄露

1、全局變量

  在瀏覽器的環境下,全局變量對象就是window,定義全局變量如下:

1 function index(){ 2   bar = "dsasd" ; 3 } 4 //上面代碼相當於
5 function index(){ 6   window.bar = "dsasd"; 7 }

如果定義變量的時候忘記加上let或var,這時一個全局變量就會被創建出來,還有另一種定義全局變量

1 function foo(){ 2     this.variable= "potential accidental global"; 3 } 4 // 函數自身發生了調用,this 指向全局對象(window),(這時候會為全局對象 window 添加一個 variable 屬性)而不是 undefined。
5 
6 foo();

解決辦法:為了防止這種錯誤的發生,在 JavaScript 文件開頭添加 'use strict'; 語句。這個語句實際上開啟了解釋 JavaScript 代碼的嚴格模式,這種模式可以避免創建意外的全局變量。

2、定時器和回調函數

  定時器造成內存泄露的主要原因是周期函數一直在運行,處理函數並不會被回收(只有周期函數停止運行之后才開始回收內存)。如果周期處理函數不能被回收,它的依賴程序也同樣無法被回收。這意味着一些資源,也許是一些相當大的數據都也無法被回收。

  觀察者即監聽事件,當它們不再被需要的時候(或者關聯對象將要失效的時候)顯式地將他們移除是十分重要的。現在,當觀察者對象失效的時候便會被回收,即便 listener 沒有被明確地移除,絕大多數的瀏覽器可以或者將會支持這個特性。盡管如此,在對象被銷毀之前移除觀察者依然是一個好的實踐。

3、DOM 之外的引用

4、閉包

 

內存泄露可以詳細看:https://blog.csdn.net/fay462298322/article/details/53172176


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM