溫馨提示×

溫馨提示×

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

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

JavaScript中的內(nèi)存管理方法是什么

發(fā)布時間:2023-04-20 09:43:34 來源:億速云 閱讀:113 作者:iii 欄目:web開發(fā)

今天小編給大家分享一下JavaScript中的內(nèi)存管理方法是什么的相關(guān)知識點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

數(shù)據(jù)類型

JavaScript 數(shù)據(jù)類型分為 基本類型引用類型。

JavaScript中的內(nèi)存管理方法是什么

基本類型:在語言最低層且不可變的值稱為原始值。所有原始值都可以使用 typeof 運(yùn)算符測試所屬基本類型(除了null,因?yàn)閠ypeof null === "object")。所有原始值都有它們相應(yīng)的對象包裝類(除了 null 和 undefined),這為原始值提供可用的方法?;绢愋偷膶ο蟀b類有 Boolean、Number、String、Symbol。

引用類型:表示內(nèi)存中的可變的值,JavaScript 中對象是唯一可變的。Object、Array、函數(shù)等都屬于對象。給對象定義屬性可通過 Object.defineProperty() 方法,讀取對象屬性信息可通過 Object.getOwnPropertyDescriptor()。

基本類型與引用類型可以互轉(zhuǎn),轉(zhuǎn)換的行為稱為 裝箱拆箱

裝箱:基本類型 => 引用類型 e.g: new String('call_me')

拆箱:引用類型 => 基本類型 e.g: new String('64').valueOf()、new String('64').toString()

以下是一些開發(fā)過程中常見的類型轉(zhuǎn)換:

  • number -> string: let a = 1 => a+"" / String(a)

  • string -> number: let a = "1" => +a / ~~a / Number(a)

  • any -> boolean: let a = {} => !a / !!a / Boolean(a)

從內(nèi)存角度區(qū)分基本類型與應(yīng)用類型,關(guān)鍵在于值在內(nèi)存中是否可變,基本類型更新會重新開辟空間并改變指針地址,引用類型更新不會改變指針地址但會改變指針?biāo)赶虻膶ο?;從代碼上看,引用類型由基本類型和 {} 組成。

數(shù)據(jù)結(jié)構(gòu)

JavaScript中的內(nèi)存管理方法是什么

JavaScript 程序運(yùn)行時 V8 會給程序分配內(nèi)存,這種內(nèi)存稱為 Resident Set(常駐內(nèi)存集合),V8 常駐內(nèi)存進(jìn)一步細(xì)分成 StackHeap。

Stack(棧) 是自動分配大小固定的內(nèi)存空間,并由系統(tǒng)自動釋放。棧數(shù)據(jù)結(jié)構(gòu)遵循先進(jìn)后出的原則,線性有序存儲,容量小,系統(tǒng)分配效率高。

Heap(堆) 是動態(tài)分配大小不固定的內(nèi)存空間,不會自動釋放(釋放依賴 GC)。堆數(shù)據(jù)結(jié)構(gòu)是一棵二叉樹結(jié)構(gòu),容量大,速度慢。

一個線程只有一個棧內(nèi)存空間,一個進(jìn)程只有一個堆空間。

棧內(nèi)存空間默認(rèn)大小是 864KB,也可通過 node --v8-options | grep -B0 -A1 stack-size 查看。

棧結(jié)構(gòu)其實(shí)經(jīng)??梢钥吹剑?dāng)寫了一段報(bào)錯代碼時,控制臺的錯誤提示就是一個棧結(jié)構(gòu)。從下往上看調(diào)用路徑,最頂部就是錯誤位置。例如最頂部拋出 Maxium call stack size exceeded 錯誤就代表當(dāng)前調(diào)用超出了棧的限制。

堆中的結(jié)構(gòu)劃分為 新空間(New Space)舊空間(Old Space)、大型對象空間(Large object space)、代碼空間(Code-space)、單元空間(Cell Space)屬性單元空間(Property Cell Space)映射空間(Map Space),新空間和舊空間在后面會詳細(xì)介紹。

大型對象空間(Large object space):大于其他空間大小限制的對象存放在這里。每個對象都有自己的內(nèi)存區(qū)域,這里的對象不會被垃圾回收器移動。

代碼空間(Code-space):存儲已編譯的代碼塊,是唯一可執(zhí)行的內(nèi)存空間。

單元空間(Cell Space)、屬性單元空間(Property Cell Space)和映射空間(Map Space):這些空間分別存放 Cell,PropertyCell 和 Map。這些空間包含的對象大小相同,并且對對象類型有些限制,可以簡化回收工作。

每個空間(除了大型對象空間(Large object space))都由若干個 Page 組成。一個 page 是由操作系統(tǒng)分配的一個連續(xù)內(nèi)存塊,一個內(nèi)存塊大小為 1MB

從內(nèi)存角度區(qū)分棧與堆,關(guān)鍵在于用完是否立即釋放。

相信讀者們看到這里肯定會聯(lián)想到數(shù)據(jù)類型與堆棧的關(guān)聯(lián),網(wǎng)上和一些書籍的結(jié)論是:原始值分配在棧上,而對象分配在堆上。這個說法真的對嗎?帶著問題我們進(jìn)入第二步:使用分配的內(nèi)存。

內(nèi)存模型

Node 提供了 process.memoryUsage() 方法描述 Node.js 進(jìn)程的內(nèi)存使用情況(以字節(jié) Bytes 為單位)

$ node
> process.memoryUsage()

假設(shè)原始值分配在棧上,而對象分配在堆上是對的,結(jié)合??臻g只有 864KB。如果我們聲明一個 10MB 的字符串,看看堆內(nèi)存是否會發(fā)生變化。

const beforeMemeryUsed = process.memoryUsage().heapUsed / 1024 / 1024;

const bigString = 'x'.repeat(10*1024*1024) // 10 MB
console.log(bigString); // need to use the string otherwise the compiler would just optimize it into nothingness

const afterMemeryUsed = process.memoryUsage().heapUsed / 1024 / 1024;

console.log(`Before memory used: ${beforeMemeryUsed} MB`); // Before memory used: 3.7668304443359375 MB
console.log(`After memory used: ${afterMemeryUsed} MB`); // After memory used: 13.8348388671875 MB

堆內(nèi)存消耗接近 10 MB,說明字符串存儲在堆中

那么小字符串以及其他基本類型是否同樣的存儲在堆中呢,我們借助谷歌瀏覽器的 Memery 堆快照(Heap snapshot)進(jìn)行分析。

打開谷歌瀏覽器無痕模式 Console 中輸入以下代碼,并分析執(zhí)行前后的變量變化。

function testHeap() {
    const smi = 18;
    const heapNumber = 18.18;
    const nextHeapNumber = 18.18;
    const boolean = true;
    const muNull = null;
    const myUndefined = undefined;
    const symbol = Symbol("my-symbol");
    const emptyString = "";
    const string = "my-string";
    const nextString = "my-string";
}
testHeap()

JavaScript中的內(nèi)存管理方法是什么

從圖中可以看出函數(shù)執(zhí)行后堆中變量分配情況。小數(shù)、字符串、symbol 都開辟了堆空間,說明分配在堆中

有兩個相同的"my-string"字符串,但并沒有重復(fù)開辟兩個字符串空間,因?yàn)?v8 內(nèi)部存在名為 stringTable 的 hashmap 緩存了所有字符串,在 V8 閱讀代碼并轉(zhuǎn)換為 AST 時,每遇到一個字符串都會換算為一個 hash 值插入到 hashmap 中。所以在我們創(chuàng)建字符串的時候,V8 會先從內(nèi)存哈希表中查找是否有已經(jīng)創(chuàng)建的完全一致的字符串,若存在,直接復(fù)用。若不存在,則開辟一塊新的內(nèi)存空間存儲。這也是為什么字符串是不可變的,修改字符串時需要重新開辟新的空間而不能再原來的空間上作修改。

小整數(shù)、boolean、undefined、null、空字符串并沒有額外開辟空間,對這些數(shù)據(jù)類型有兩種猜測:

  1. 存放在棧空間中;

  2. 存放在堆中但在系統(tǒng)啟動時就已經(jīng)開辟。

JavaScript中的內(nèi)存管理方法是什么

JavaScript中的內(nèi)存管理方法是什么JavaScript中的內(nèi)存管理方法是什么

其實(shí) V8 中有一個特殊的原始值子集,稱為 Oddball。它們在運(yùn)行之前由 V8 預(yù)先分配在堆上,無論 JavaScript 程序是否實(shí)際使用到它們。從整個堆空間查看這些類型的分配,boolean、undefined、null、空字符串分配在堆內(nèi)存中且屬于 Oddball 類型。無論何時分配空間對應(yīng)的內(nèi)存地址永遠(yuǎn)是固定的(空字符串@77、null@71、undefined@67、true@73)。但并未找到小整數(shù),證明函數(shù)局部變量小整數(shù)存在棧中,但定義在全局中的小整數(shù)則是分配在堆中

同樣都是表示 Number 類型,小整數(shù)和小數(shù)在存儲上有什么區(qū)別呢?

一般編程語言在區(qū)分 Number 類型時需要關(guān)心 Int、Float、32、64。在 JavaScript 中統(tǒng)稱為 Number,但 v8 內(nèi)部對 Number 類型的實(shí)現(xiàn)可沒看起來這么簡單,在 V8 內(nèi)部 Number 分為 smiheapNumber,分別用于存儲小整數(shù)與小數(shù)(包括大整數(shù))。ECMAScript 標(biāo)準(zhǔn)約定 Number 需要被當(dāng)成 64 位雙精度浮點(diǎn)數(shù)處理,但事實(shí)上一直使用 64 位去存儲任何數(shù)字在時間和空間上非常低效的,并且 smi 大量使用位運(yùn)算,所以為了提高性能 JavaScript 引擎在存儲 smi 的時候使用 32 位去存儲數(shù)字而 heapNumber 使用 32 位或 64 位存儲數(shù)字。

以上是局部變量在函數(shù)中的內(nèi)存分布,接下來驗(yàn)證對象的內(nèi)存分布。谷歌瀏覽器無痕模式 Console 中輸入以下代碼,并在 Class filter 中輸入 TestClass 查看其內(nèi)存分布情況。

function TestClass() {
    this.number = 123;
    this.number2 = 123;
    this.heapNumebr = 123.18;
    this.heapNumber2 = 123.18;
    this.string = "abc";
    this.string2 = "abc";
    this.boolean = true;
    this.symbol = Symbol('test')
    this.undefined = undefined;
    this.null = null
    this.object = { name: 'pp' }
    this.array = [1, 2, 3];
}
let testobject = new TestClass()

JavaScript中的內(nèi)存管理方法是什么

和上一個案例不同的是內(nèi)存中多了 smi number 類型。由于對象本身就存儲在堆中,所以小整數(shù)也存儲在堆中。shallow size 大小為 0,證明了小整數(shù)雖在堆中卻不占內(nèi)存空間。是什么原因?qū)е滦≌麛?shù)不占內(nèi)存空間?

這和 V8 中使用 指針標(biāo)記技術(shù) 有關(guān),指針標(biāo)記技術(shù)使得指針標(biāo)記位可以存儲地址或者標(biāo)記值。整數(shù)的值直接存儲在指針中,而不必為其分配額外的存儲空間;對象的值需要開辟額外內(nèi)存空間,指針中存放其地址。這也導(dǎo)致了對象中的小整數(shù)數(shù)值相同地址也相同。

|------ 32位架構(gòu) -----|
|_____address_____ w1| 指針
|___int31_value____ 0| Smi

|---------------- 64位架構(gòu) ----------------|
|________base________|_____offset______ w1| 指針
|--------------------|___int31_value____ 0| Smi

V8 使用最低有效位來區(qū)分 Smi 和對象指針。對于對象指針,它使用第二個最低有效位來區(qū)分強(qiáng)引用弱引用

在 32 位架構(gòu)中 Smi 值只能攜帶 31 位有效載荷。包括符號位,Int32類型的范圍是 -(2^31) ~ 2^31 - 1, 所以Smi的范圍實(shí)際上是Int31類型的范圍(-(2^30) ~ 2^30 - 1)。對象指針有 30 位可用作堆對象地址有效負(fù)載。

由于單線程和 v8 垃圾回收機(jī)制的限制,內(nèi)存越大回收的過程中 JavaScript 線程會阻塞且嚴(yán)重影響程序的性能和響應(yīng)能力,出于性能以及避免空間浪費(fèi)的考慮,大部分瀏覽器以及 Node15+ 的內(nèi)存上限為 4G(4G 剛好是 2^32 byte)。以內(nèi)存上限為 4G 為例,V8 中的堆布局需要保證無論是 64 位系統(tǒng)還是 32 位系統(tǒng)都只使用32位的空間來儲存。在 64 位架構(gòu)中 Smi 同樣使用 31 位有效負(fù)載,與 32 位架構(gòu)保持一致;對象指針使用 62 位有效負(fù)載,其中前 32 位表示 base(基址),其值指向 4G 內(nèi)存中間段的地址。后 32 位的前 30 位表示 offset,指前后 2G 內(nèi)存空間的偏移量。

v8 可以通過以下代碼查看內(nèi)存上限。

const v8 = require('v8')
console.log('heap_size_limit:',v8.getHeapStatistics().heap_size_limit) // 查詢堆內(nèi)存上限設(shè)置,不同 node 版本默認(rèn)設(shè)置是不一樣

通過設(shè)置環(huán)境 export NODE_OPTIONS=--max_old_space_size=8192 或者啟動時傳遞 --max-old-space-size(或 --max-new-space-size)參數(shù)修改內(nèi)存上限。

通過以上兩個案例,細(xì)心的讀者可能已經(jīng)發(fā)現(xiàn) heap number 作為函數(shù)私有變量時存在復(fù)用但作為對象的屬性時不存在復(fù)用(地址不相同)。作者猜測函數(shù)中的私有變量做了類似字符串的 hashmap 優(yōu)化,而作為對象屬性時為了避免每次修改變量重新開辟空間而導(dǎo)致內(nèi)存消耗大,無論數(shù)值是否相同都會重新開辟空間,修改時直接修改指針?biāo)赶虻木唧w值。

以執(zhí)行函數(shù)為例簡單概括 JavaScript 的內(nèi)存模型

JavaScript中的內(nèi)存管理方法是什么

垃圾回收機(jī)制及策略

使用完內(nèi)存我們需要對內(nèi)存進(jìn)行釋放以及歸還,像 C 語言這樣的底層語言一般都有底層的堆內(nèi)存管理接口,比如 malloc() 和 free()。相反,JavaScript 是在創(chuàng)建變量(對象,字符串等)時自動進(jìn)行了分配內(nèi)存,并且在不使用它們時"自動"釋放。釋放的過程稱為 垃圾回收。釋放過程不是實(shí)時的,因?yàn)槠溟_銷比較大,所以垃圾回收器會按照固定的時間間隔周期性的執(zhí)行,這讓 JavaScript 開發(fā)者錯誤的認(rèn)為可以不關(guān)心垃圾回收機(jī)制及策略。

引用計(jì)數(shù)法

這是最初級的垃圾收集算法。此算法把"對象是否不再需要"簡化定義為"對象有沒有其他對象引用到它"。假設(shè)有一個對象A,任何一個對象對A的引用,那么對象A的引用計(jì)數(shù)器+1,當(dāng)引用清除時,對象A的引用計(jì)數(shù)器就-1,如果對象A的計(jì)算器的值為 0,就說明對象A沒有引用了,可以被回收。

但該算法有個限制:無法處理循環(huán)引用問題。在下面的例子中,兩個對象被創(chuàng)建,并互相引用,形成了一個循環(huán)。它們被調(diào)用之后會離開函數(shù)作用域,所以它們已經(jīng)沒有用了,可以被回收了。然而,引用計(jì)數(shù)算法考慮到它們互相都有至少一次引用,所以它們不會被回收。

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o
  return "";
}
f();
標(biāo)記清除法

這個算法把"對象是否不再需要"簡化定義為"對象是否可達(dá)",解決了循環(huán)引用的問題。這個算法假定設(shè)置一個叫做根(root)的對象(在 Javascript 里,根是全局對象)。垃圾回收器將定期從根開始,不具備可達(dá)性的元素將被回收??蛇_(dá)性指的是一個變量是否能夠直接或間接通過全局對象訪問到,如果可以那么該變量就是可達(dá)的,否則就是不可達(dá)。

但標(biāo)記清除法對比引用計(jì)數(shù)法 缺乏時效性,只有在有效內(nèi)存空間耗盡了,V8引擎將會停止應(yīng)用程序的運(yùn)行并開啟 GC 線程,然后開始進(jìn)行標(biāo)記工作。所以這種方式效率低,標(biāo)記和清除都需要遍歷所有對象,并且在 GC 時,需要停止應(yīng)用程序,對于交互性要求比較高的應(yīng)用而言這個體驗(yàn)是非常差的;通過標(biāo)記清除算法清理出來的內(nèi)容碎片化較為嚴(yán)重,因?yàn)楸换厥盏膶ο罂赡艽嬖谟趦?nèi)存的各個角落,所以清理出來的內(nèi)存是不連貫的。

標(biāo)記壓縮算法

標(biāo)記壓縮算法是在標(biāo)記清除算法的基礎(chǔ)之上,做了優(yōu)化改進(jìn)的算法。和標(biāo)記清除算法一樣,也是從根節(jié)點(diǎn)開始,對對象的引用進(jìn)行標(biāo)記,在清理階段,并不是簡單的清理未標(biāo)記的對象,而是將存活的對象壓縮到內(nèi)存的一端,然后清理邊界以外的垃圾,從而解決了碎片化的問題。

標(biāo)記壓縮算法解決了標(biāo)記清除算法的碎片化的問題,同時,標(biāo)記壓縮算法多了一步,對象移動內(nèi)存位置的步驟,其效率也有一定的影響。

增量標(biāo)記法

標(biāo)記壓縮算法只解決了標(biāo)記清除法的內(nèi)存碎片化問題,但是沒有解決停頓問題。為了減少全停頓的時間,V8 使用了如下優(yōu)化,改進(jìn)后,最大停頓時間減少到原來的1/6。

  1. 增量 GC:GC 是在多個增量步驟中完成,而不是一步完成。

  2. 并發(fā)標(biāo)記: 標(biāo)記空間的對象哪些是活的哪些是死的是使用多個輔助線程并發(fā)進(jìn)行,不影響 JavaScript 的主線程。

  3. 并發(fā)清掃/壓縮:清掃和壓縮也是在輔助線程中并發(fā)進(jìn)行,不影響 JavaScript 的主線程。

  4. 延遲清掃:延遲刪除垃圾,直到有內(nèi)存需求或者主線程空閑時再刪除。

V8引擎垃圾回收策略

JavaScript 中的 垃圾回收策略采用分代回收的思想。Heap(堆)內(nèi)存中只有新空間(New Space)和舊空間(Old Space)由 GC 管理。

新空間(New Space):新對象存活的地方,駐留在此處的對象稱為New Generation(新生代)。Minor GC 作為該空間的回收機(jī)制,該空間采用 Scavenge 算法 + 標(biāo)記清除法

  • Minor GC 保持新空間的緊湊和干凈,其中有一個分配指針,每當(dāng)我們想為新的對象分配內(nèi)存空間時,就會遞增這個指針。當(dāng)該指針達(dá)到新空間的末端時,就會觸發(fā)一次 Minor GC。這個過程也被稱為 Scavenger,它實(shí)現(xiàn)了 Cheney 算法。由于空間很?。?-8MB 之間)導(dǎo)致 Minor GC 經(jīng)常被觸發(fā),所以這些對象的生命周期都很短,而且 Minor GC 過程使用并行的輔助線程,速度非??欤瑑?nèi)存分配的成本很低。

  • 新空間由兩個大小 Semi-Space 組成,為了區(qū)分二者 Minor GC 將二者命名為 from-spaceto-space。內(nèi)存分配發(fā)生在 from-space 空間,當(dāng) from-space 空間被填滿時,就會觸發(fā) Minor GC。將還存活著的對象遷移到 to-space 空間,并將 from-space 和 to-space 的名字交換一下,交換后所有的對象都在 from-space 空間,to-space 空間是空的。一段時間后 from-space 又被填滿時再次觸發(fā) Minor GC,第二次存活的對象將會被遷移到舊空間(Old Space),第一次存活下來的新對象被遷移到 to-space 空間,如此周而復(fù)始操作就形成了 Minor GC 的過程。

舊空間(Old Space):在新空間(New Space)被兩次 Minor GC 后依舊存活的對象會被遷移到這里,駐留在此處的對象稱為Old Generation(老生代)。 Major GC 作為該空間的回收機(jī)制,該空間采用標(biāo)記清除、標(biāo)記壓縮、增量標(biāo)記算法

  • V8 根據(jù)某種算法計(jì)算,確定沒有足夠的舊空間就會觸發(fā) Major GC。Cheney 算法對于小數(shù)據(jù)量來說是完美的,但對于 Heap 中的舊空間來說是不切實(shí)際的,因?yàn)樗惴ū旧碛袃?nèi)存開銷,所以 Major GC 使用標(biāo)記清除、標(biāo)記壓縮、增量標(biāo)記算法。

  • 舊空間分為舊址針空間和舊數(shù)據(jù)空間:舊指針空間包含具有指向其他對象的指針的對象;舊數(shù)據(jù)空間包含數(shù)據(jù)的對象(沒有指向其他對象的指針)。

內(nèi)存泄漏

并不是所有內(nèi)存都會被回收,當(dāng)程序運(yùn)行時由于某種原因未能被 GC 而造成內(nèi)存空間的浪費(fèi)稱為 內(nèi)存泄漏。輕微的內(nèi)存泄漏或許不太會對程序造成什么影響,嚴(yán)重的內(nèi)存泄漏則會影響程序的性能,甚至導(dǎo)致程序的崩潰。

以下是一些導(dǎo)致內(nèi)存泄漏的場景

閉包
var theThing = null;
const replaceThing = function () { 
  var originalThing = theThing; 
  
  var unused = function () { 
    if (originalThing) 
      console.log("hi"); 
  }; 
  
  theThing = { 
    longStr: new Array(1000000).join('*'), 
    someMethod: function () { 
      console.log("someMessage"); 
    } 
  };
  // 如果在此處添加 `originalThing = null`,則不會導(dǎo)致內(nèi)存泄漏。
};
setInterval(replaceThing, 1000);

這是一個非常經(jīng)典的閉包內(nèi)存泄漏案例,unused 中引用了 originalThing,所以強(qiáng)制它保持活動狀態(tài),阻止了它的回收。unused 本身并未被使用所以函數(shù)執(zhí)行結(jié)束后會被 gc 回收。但 somemethod 與 unused 在同一個上下文,共享閉包范圍。每次執(zhí)行 replaceThing 時閉包函數(shù) someMethod 中都會引用上一個 theThing 對象。

意外的全局變量
function foo(arg) { 
    bar = "隱式全局變量"; 
}
// 等同于:
function foo(arg) { 
    window.bar = "顯式全局變量"; 
}

定義大量的全局變量會導(dǎo)致內(nèi)存泄漏。在瀏覽器中全局對象是“ window”。在 NodeJs 中全局對象是“global”或“process”。此處變量 bar 永遠(yuǎn)無法被收集。

還有一種情況是使用 this 生成全局變量。

function fn () {
    this.bar = "全局變量"; // 這里的  this 的指向 window, 因此 bar 同樣會被掛載到 window 對象下
}
fn();

避免此問題的辦法是在文件頭部或者函數(shù)的頂部加上 'use strict', 開啟嚴(yán)格模式使得 this 的指向?yàn)?undefined。

若必須使用全局變量存儲大量數(shù)據(jù)時,確保用完后設(shè)置為 null 即可。

忘記清除定時器

setInterval/setTimeout 未被清除會導(dǎo)致內(nèi)存泄漏。在執(zhí)行 clearInterval/clearTimeout 之前,系統(tǒng)不會釋放 setInterval/setTimeout 回調(diào)函數(shù)中的變量,及時釋放內(nèi)存就需要手動執(zhí)行clearInterval/clearTimeout。

若 setTimeout 執(zhí)行完成則沒有內(nèi)存泄漏的問題,因?yàn)閳?zhí)行結(jié)束后就會立即釋放內(nèi)存。

忘記清除事件監(jiān)聽器

當(dāng)組件掛載事件處理函數(shù)后,在組件銷毀時不主動將其清除,事件處理函數(shù)被認(rèn)為是需要的而不會被 GC。如果內(nèi)部引用的變量存儲了大量數(shù)據(jù),可能會引起頁面占用內(nèi)存過高,造成內(nèi)存泄漏。

忘記清除 DOM 引用

把 DOM 存儲在字典(JSON 鍵值對)或者數(shù)組中,當(dāng)元素從 DOM 中刪除時,而 DOM 的引用還是存于內(nèi)存中,則 DOM 的引用不會被 GC 回收而需要手動清除,所以存儲 DOM 通常使用弱引用的方式。

舊版瀏覽器和錯誤擴(kuò)展

舊版瀏覽器 (IE6–7) 因無法處理 DOM 對象和 JavaScript 對象之間的循環(huán)引用而導(dǎo)致內(nèi)存泄漏。

有時錯誤的瀏覽器擴(kuò)展可能會導(dǎo)致內(nèi)存泄漏。

內(nèi)存泄漏排查

若程序運(yùn)行一段時間后慢慢變卡甚至崩潰,就要開始排查、定位以及修復(fù)內(nèi)存泄漏,常用的內(nèi)存泄漏排查方式有四種:

  1. 使用 Chrome 瀏覽器的 Performance 查看是否存在內(nèi)存泄漏,使用 Memory 定位泄漏源。

  2. 使用 Node.js 提供的 process.memoryUsage 方法,查看 heapUsed 走勢;

  3. 使用node --inspect xxx.js啟動服務(wù)并訪問chrome://inspect,打開 Memory 定位泄漏源;

  4. 應(yīng)用接入 grafana 的前提下,可通過 ab 壓測觀察 grafana 內(nèi)存走勢。

內(nèi)存分布對大部分開發(fā)者來說都是一個黑盒,v8 中實(shí)現(xiàn)的 JavaScript 內(nèi)存模型非常復(fù)雜,99% 的開發(fā)都不用去關(guān)心,甚至在 ECMAScript 規(guī)范中也沒有找到任何關(guān)于內(nèi)存布局的信息。若是興趣使然,完全可以看看 v8 引擎的源碼。在工作中如果你已經(jīng)開始專研 JavaScript 內(nèi)存分布的問題,說明你有能力開始編寫更底層的語言了。

以上就是“JavaScript中的內(nèi)存管理方法是什么”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學(xué)習(xí)更多的知識,請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI