91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JavaScript內存泄漏實例分析

發布時間:2022-02-07 09:32:49 來源:億速云 閱讀:113 作者:iii 欄目:web開發

這篇文章主要講解了“JavaScript內存泄漏實例分析”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“JavaScript內存泄漏實例分析”吧!

JavaScript內存泄漏實例分析

js 內存泄漏

什么是內存泄漏?

程序的運行需要內存。只要程序提出要求,操作系統或者運行時(runtime)就必須供給內存。

對于持續運行的服務進程(daemon),必須及時釋放不再用到的內存。否則,內存占用越來越高,輕則影響系統性能,重則導致進程崩潰。
JavaScript內存泄漏實例分析
不再用到的內存,沒有及時釋放,就叫做內存泄漏(memory leak)。

有些語言(比如 C 語言)必須手動釋放內存,程序員負責內存管理。

char * buffer;buffer = (char*) malloc(42);// Do something with bufferfree(buffer);

上面是 C 語言代碼,malloc方法用來申請內存,使用完畢之后,必須自己用free方法釋放內存。

這很麻煩,所以大多數語言提供自動內存管理,減輕程序員的負擔,這被稱為"垃圾回收機制"(garbage collector)。

雖然前端有垃圾回收機制,但當某塊無用的內存,卻無法被垃圾回收機制認為是垃圾時,也就發生內存泄漏了。

哪些情況會引起內存泄漏

1. 意外的全局變量

全局變量的生命周期最長,直到頁面關閉前,它都存活著,所以全局變量上的內存一直都不會被回收。

當全局變量使用不當,沒有及時回收(手動賦值 null),或者拼寫錯誤等將某個變量掛載到全局變量時,也就發生內存泄漏了。

2. 遺忘的定時器
setTimeout 和 setInterval 是由瀏覽器專門線程來維護它的生命周期,所以當在某個頁面使用了定時器,當該頁面銷毀時,沒有手動去釋放清理這些定時器的話,那么這些定時器還是存活著的。

也就是說,定時器的生命周期并不掛靠在頁面上,所以當在當前頁面的 js 里通過定時器注冊了某個回調函數,而該回調函數內又持有當前頁面某個變量或某些 DOM 元素時,就會導致即使頁面銷毀了,由于定時器持有該頁面部分引用而造成頁面無法正常被回收,從而導致內存泄漏了。

如果此時再次打開同個頁面,內存中其實是有雙份頁面數據的,如果多次關閉、打開,那么內存泄漏會越來越嚴重。而且這種場景很容易出現,因為使用定時器的人很容易遺忘清除。

3. 使用不當的閉包
函數本身會持有它定義時所在的詞法環境的引用,但通常情況下,使用完函數后,該函數所申請的內存都會被回收了。

但當函數內再返回一個函數時,由于返回的函數持有外部函數的詞法環境,而返回的函數又被其他生命周期東西所持有,導致外部函數雖然執行完了,但內存卻無法被回收。

4. 遺漏的 DOM 元素
DOM 元素的生命周期正常是取決于是否掛載在 DOM 樹上,當從 DOM 樹上移除時,也就可以被銷毀回收了
但如果某個 DOM 元素,在 js 中也持有它的引用時,那么它的生命周期就由 js 和是否在 DOM 樹上兩者決定了,記得移除時,兩個地方都需要去清理才能正常回收它。

5. 網絡回調
某些場景中,在某個頁面發起網絡請求,并注冊一個回調,且回調函數內持有該頁面某些內容,那么,當該頁面銷毀時,應該注銷網絡的回調,否則,因為網絡持有頁面部分內容,也會導致頁面部分內容無法被回收。


如何監控內存泄漏

內存泄漏是可以分成兩類的,一種是比較嚴重的,泄漏的就一直回收不回來了,另一種嚴重程度稍微輕點,就是沒有及時清理導致的內存泄漏,一段時間后還是可以被清理掉。

不管哪一種,利用開發者工具抓到的內存圖,應該都會看到一段時間內,內存占用不斷的直線式下降,這是因為不斷發生 GC,也就是垃圾回收導致的。

內存不足會造成不斷 GC,而 GC 時是會阻塞主線程的,所以會影響到頁面性能,造成卡頓,所以內存泄漏問題還是需要關注的。

場景一:在某個函數內申請一塊內存,然后該函數在短時間內不斷被調用

// 點擊按鈕,就執行一次函數,申請一塊內存startBtn.addEventListener("click", function() {
	var a = new Array(100000).fill(1);
	var b = new Array(20000).fill(1);});

JavaScript內存泄漏實例分析

一個頁面能夠使用的內存是有限的,當內存不足時,就會觸發垃圾回收機制去回收沒用的內存。

而在函數內部使用的變量都是局部變量,函數執行完畢,這塊內存就沒用可以被回收了。

所以當我們短時間內不斷調用該函數時,可以發現,函數執行時,發現內存不足,垃圾回收機制工作,回收上一個函數申請的內存,因為上個函數已經執行結束了,內存無用可被回收了。

所以圖中呈現內存使用量的圖表就是一條橫線過去,中間出現多處豎線,其實就是表示內存清空,再申請,清空再申請,每個豎線的位置就是垃圾回收機制工作以及函數執行又申請的時機。

場景二:在某個函數內申請一塊內存,然后該函數在短時間內不斷被調用,但每次申請的內存,有一部分被外部持有。

// 點擊按鈕,就執行一次函數,申請一塊內存var arr = [];startBtn.addEventListener("click", function() {
	var a = new Array(100000).fill(1);
	var b = new Array(20000).fill(1);
    arr.push(b);});

JavaScript內存泄漏實例分析

看一下跟第一張圖片有什么區別?

不再是一條橫線了吧,而且橫線中的每個豎線的底部也不是同一水平了吧。

其實這就是內存泄漏了。

我們在函數內申請了兩個數組內存,但其中有個數組卻被外部持有,那么,即使每次函數執行完,這部分被外部持有的數組內存也依舊回收不了,所以每次只能回收一部分內存。

這樣一來,當函數調用次數增多時,沒法回收的內存就越多,內存泄漏的也就越多,導致內存使用量一直在增長
另外,也可以使用 performance monitor 工具,在開發者工具里找到更多的按鈕,在里面打開此功能面板,這是一個可以實時監控 cpu,內存等使用情況的工具,會比上面只能抓取一段時間內工具更直觀一點:
JavaScript內存泄漏實例分析

梯狀上升的就是發生內存泄漏了,每次函數調用,總有一部分數據被外部持有導致無法回收,而后面平滑狀的則是每次使用完都可以正常被回收。

這張圖需要注意下,第一個紅框末尾有個直線式下滑,這是因為,我修改了代碼,把外部持有函數內申請的數組那行代碼去掉,然后刷新頁面,手動點擊 GC 才觸發的效果,否則,無論你怎么點 GC,有部分內存一直無法回收,是達不到這樣的效果圖的。

以上,是監控是否發生內存泄漏的一些工具,但下一步才是關鍵,既然發現內存泄漏,那該如何定位呢?如何知道,是哪部分數據沒被回收導致的泄漏呢?

如何分析內存泄漏,找出有問題的代碼

分析內存泄漏的原因,還是需要借助開發者工具的 Memory 功能,這個功能可以抓取內存快照,也可以抓取一段時間內,內存分配的情況,還可以抓取一段時間內觸發內存分配的各函數情況。
JavaScript內存泄漏實例分析
利用這些工具,我們可以分析出,某個時刻是由于哪個函數操作導致了內存分配,分析出大量重復且沒有被回收的對象是什么。

這樣一來,有嫌疑的函數也知道了,有嫌疑的對象也知道了,再去代碼中分析下,這個函數里的這個對象到底是不是就是內存泄漏的元兇,搞定。

先舉個簡單例子,再舉個實際內存泄漏的例子:

場景一:在某個函數內申請一塊內存,然后該函數在短時間內不斷被調用,但每次申請的內存,有一部分被外部持有

// 每次點擊按鈕,就有一部分內存無法回收,因為被外部 arr 持有了var arr = [];startBtn.addEventListener("click", function() {
	var a = new Array(100000).fill(1);
	var b = new Array(20000).fill(1);
  arr.push(b);});

JavaScript內存泄漏實例分析
可以抓取兩份快照,兩份快照中間進行內存泄漏操作,最后再比對兩份快照的區別,查看增加的對象是什么,回收的對象又是哪些,如上圖。

也可以單獨查看某個時刻快照,從內存占用比例來查看占據大量內存的是什么對象,如下圖:
JavaScript內存泄漏實例分析
還可以從垃圾回收機制角度出發,查看從 GC root 根節點出發,可達的對象里,哪些對象占用大量內存:
JavaScript內存泄漏實例分析
從上面這些方式入手,都可以查看到當前占用大量內存的對象是什么,一般來說,這個就是嫌疑犯了。

當然,也并不一定,當有嫌疑對象時,可以利用多次內存快照間比對,中間手動強制 GC 下,看下該回收的對象有沒有被回收,這是一種思路。

  • 抓取一段時間內,內存分配情況。
    JavaScript內存泄漏實例分析

這個方式,可以有選擇性的查看各個內存分配時刻是由哪個函數發起,且內存存儲的是什么對象。

當然,內存分配是正常行為,這里查看到的還需要借助其他數據來判斷某個對象是否是嫌疑對象,比如內存占用比例,或結合內存快照等等。

  • 抓取一段時間內函數的內存使用情況
    JavaScript內存泄漏實例分析

這個能看到的內容很少,比較簡單,目的也很明確,就是一段時間內,都有哪些操作在申請內存,且用了多少。

總之,這些工具并沒有辦法直接給你答復,告訴你 xxx 就是內存泄漏的元兇,如果瀏覽器層面就能確定了,那它干嘛不回收它,干嘛還會造成內存泄漏

所以,這些工具,只能給你各種內存使用信息,你需要自己借助這些信息,根據自己代碼的邏輯,去分析,哪些嫌疑對象才是內存泄漏的元兇。

實例分析

例子1:

var t = null;var replaceThing = function() {
  var o = t  var unused = function() {
    if (o) {
      console.log("hi")
    }        
  }
 
  t = {
    longStr: new Array(100000).fill('*'),
    someMethod: function() {
      console.log(1)
    }
  }}setInterval(replaceThing, 1000)

先說說這代碼用途,聲明了一個全局變量 t 和 replaceThing 函數,函數目的在于會為全局變量賦值一個新對象,然后內部有個變量存儲全局變量 t 被替換前的值,最后定時器周期性執行 replaceThing 函數

  • 發現問題

我們先利用工具看看,是不是會發生內存泄漏:
JavaScript內存泄漏實例分析
三種內存監控圖表都顯示,這發生內存泄漏了:反復執行同個函數,內存卻梯狀式增長,手動點擊 GC 內存也沒有下降,說明函數每次執行都有部分內存泄漏了。

這種手動強制垃圾回收都無法將內存將下去的情況是很嚴重的,長期執行下去,會耗盡可用內存,導致頁面卡頓甚至崩掉。

  • 分析問題

既然已經確定有內存泄漏了,那么接下去就該找出內存泄漏的原因了。
JavaScript內存泄漏實例分析
首先通過 sampling profile,我們把嫌疑定位到 replaceThing 這個函數上

接著,我們抓取兩份內存快照,比對一下,看看能否得到什么信息:
JavaScript內存泄漏實例分析
比對兩份快照可以發現,這過程中,數組對象一直在增加,而且這個數組對象來自 replaceThing 函數內部創建的對象的 longStr 屬性。

其實這張圖信息很多了,尤其是下方那個嵌套圖,嵌套關系是反著來,你倒著看的話,就可以發現,從全局對象 Window 是如何一步步訪問到該數組對象的,垃圾回收機制正是因為有這樣一條可達的訪問路徑,才無法回收。

其實這里就可以分析了,為了多使用些工具,我們換個圖來分析吧。

我們直接從第二份內存快照入手,看看:
JavaScript內存泄漏實例分析

為什么每一次 replaceThing 函數調用后,內部創建的對象都無法被回收呢?

因為 replaceThing 的第一次創建,這個對象被全局變量 t 持有,所以回收不了。

后面的每一次調用,這個對象都被上一個 replaceThing 函數內部的 o 局部變量持有而回收不了。

而這個函數內的局部變量 o 在 replaceThing 首次調用時被創建的對象的 someMethod 方法持有,該方法掛載的對象被全局變量 t 持有,所以也回收不了。

這樣層層持有,每一次函數的調用,都會持有函數上次調用時內部創建的局部變量,導致函數即使執行結束,這些局部變量也無法回收。

感謝各位的閱讀,以上就是“JavaScript內存泄漏實例分析”的內容了,經過本文的學習后,相信大家對JavaScript內存泄漏實例分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

庆云县| 莒南县| 济源市| 台中县| 酒泉市| 香港| 雅江县| 青阳县| 南昌县| 眉山市| 金湖县| 鄂托克旗| 泰和县| 蓝山县| 儋州市| 绥芬河市| 隆子县| 易门县| 海城市| 深泽县| 平江县| 泰安市| 秭归县| 临泉县| 丁青县| 仁化县| 嵩明县| 尚志市| 永福县| 莱芜市| 泗洪县| 孟津县| 武穴市| 延长县| 顺昌县| 绥芬河市| 梓潼县| 甘孜| 东港市| 孝义市| 哈巴河县|