目錄
js 記憶體洩漏
什麼是記憶體洩漏?
哪些情況會造成記憶體洩漏
如何監控記憶體洩漏
如何分析内存泄漏,找出有问题的代码
实例分析
首頁 web前端 js教程 完全掌握JavaScript記憶體洩漏(圖文詳解)

完全掌握JavaScript記憶體洩漏(圖文詳解)

Jan 28, 2022 pm 06:00 PM
html 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記憶體洩漏(圖文詳解)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

HTML 中的表格邊框 HTML 中的表格邊框 Sep 04, 2024 pm 04:49 PM

HTML 表格邊框指南。在這裡,我們以 HTML 中的表格邊框為例,討論定義表格邊框的多種方法。

HTML 中的巢狀表 HTML 中的巢狀表 Sep 04, 2024 pm 04:49 PM

這是 HTML 中巢狀表的指南。這裡我們討論如何在表中建立表格以及對應的範例。

HTML 左邊距 HTML 左邊距 Sep 04, 2024 pm 04:48 PM

HTML 左邊距指南。在這裡,我們討論 HTML margin-left 的簡要概述及其範例及其程式碼實作。

HTML 表格佈局 HTML 表格佈局 Sep 04, 2024 pm 04:54 PM

HTML 表格佈局指南。在這裡,我們詳細討論 HTML 表格佈局的值以及範例和輸出。

HTML 輸入佔位符 HTML 輸入佔位符 Sep 04, 2024 pm 04:54 PM

HTML 輸入佔位符指南。在這裡,我們討論 HTML 輸入佔位符的範例以及程式碼和輸出。

HTML 有序列表 HTML 有序列表 Sep 04, 2024 pm 04:43 PM

HTML 有序列表指南。在這裡我們也分別討論了 HTML 有序列表和類型的介紹以及它們的範例

HTML onclick 按鈕 HTML onclick 按鈕 Sep 04, 2024 pm 04:49 PM

HTML onclick 按鈕指南。這裡我們分別討論它們的介紹、工作原理、範例以及各個事件中的onclick事件。

在 HTML 中移動文字 在 HTML 中移動文字 Sep 04, 2024 pm 04:45 PM

HTML 中的文字移動指南。在這裡我們討論一下marquee標籤如何使用語法和實作範例。

See all articles