Sitemap
Hannah Lin

Love coding ❤

Follow publication

JavaScript’s Memory Management

12 min readMay 7, 2025

--

JS 世界有 Garbage Collection (GC) 自動幫忙管理記憶體,工程師不需要也沒辦法手動配置。但若亂寫程式,卻有可能發生 Memory leak (記憶體沒有被 release,導致效能變差) 而不自知。

為了避免寫出導致 Memory leak 的程式碼,必須要了解 JS engine 如何管理記憶體、以及 Garbage Collection 的背後機制。此文會先從 Memory life cycle (記憶體生命週期) 開始,到 Garbage Collection 是如何清除記憶體,以及一些容易導致 Memory leak 的程式碼範例,希望日後自己在寫程式時,可以避免犯這些錯誤。

🔖 文章索引

1. Memory life cycle
2. 儲存 data 的地方
3 Garbage Collection: Release
4. Memory leak
5. Debugging memory leaks

Memory life cycle

分配記憶體空間 Allocate memory → 使用 Use memory → 清除記憶體 Release memory

當建立 variables、呼叫 functions 時,JS 會自動分配到記憶體空間 (Allocate),當使用 (Use) 完不再需要它也會自動清除 (Release)。

以下列範例來說

function foo() {
const a = 1;
const b = 2;
const c = 3;
return a + b + c;
}

foo();

在執行 foo() 時,記憶體會儲存 abc ,但當 foo() 執行完畢後,GC 就會發現 abc 這三個變數不再被使用,因此就會將這三個變數所佔用的記憶體空間釋放出來。

決定清除哪些記憶體空間是 Garbage Collection (GC) 的工作,等一下會詳細提到。

儲存 data 的地方

JS engines 有 stack 跟 memory heap 兩種數據空間可以存放記憶體。寫 JS 的工程師都知道 js 的 data 分為 Primitive Type 與 Object Type

Stack 拿來放 primitive values 跟 object Type 的 references; heap 則是放 objects 跟 functions

const male = true // stack

// 以下 object 會存到 heap,但它的 reference (記憶體位置) 會存在 stack
cont people = {
name: 'Hannah'
}
// heap
function a () {}

Stack

A stack is a data structure that JavaScript uses to store static data.

All the values get stored in the stack since they all contain primitive values.

Stack 拿來存 static data,static data 因為是固定的 size 所以可以直接分配固定的記憶體空間給他們,在 JS 就是指 primitive value (strings, numbers, booleans, undefined, and null) 跟 object type 的 references。因為 static data 為固定 size,所以 Stack 有限制大小,限制多少是取決於瀏覽器的。

Heap

A heap is a data structure that JavaScript uses to store dynamic data where objects are stored when we define functions or variables.

Heap 拿來存放 object type 的實際 data,而他們的 reference 則是存在 stack。

這邊下一個小結論,對於哪些 data 放哪裏其實只要有一個概念就好,不需要太糾結 (例如有些 number 可能會存在 heap)。畢竟這是 engine 處理的事,我們管不了 XD。

Garbage Collection: Release

Garbage collection in JavaScript is an automatic memory management process that identifies and removes objects that are no longer reachable or needed by the program

GC 是一個自動化管理記憶體的機制,當某個記憶體不被使用時,GC 會自動清除並釋放記憶體空間 (using mark-and-sweep algorithm)

基本上會經過以下步驟來決定誰要被清除 (using mark-and-sweep algorithm)

  • The garbage collector takes roots and “marks” (remembers) them: 只要是 Global variables、正在執行 function 連同函示 scope 內的變數都是 roots
  • Then it visits and “marks” all references from them.
  • Then it visits marked objects and marks their references. All visited objects are remembered, so as not to visit the same object twice in the future.
  • …And so on until every reachable (from the roots) references are visited.
  • All objects except marked ones are removed.

所以像這個例子

function marry(man, woman) {
woman.husband = man;
man.wife = woman;

return {
father: man,
mother: woman
}
}

let family = marry({
name: "John"
}, {
name: "Ann"
});

一開始所有 references 都是 reachable,所以沒有東西被清除。但當刪除以下兩個 references

delete family.father;
delete family.mother.husband;

John 就再沒有 incoming reference 所以變成 unreachable 會被清除掉!這邊要注意清除機制只會考慮 incoming reference,就像這邊 John 其實還留有 Outgoing reference 但不影響被清除掉。

若不是同時刪而只刪了一個 reference

delete family.father;

此時 John 還是有 Ann 的 husband 這個 reference,所以並不會被清除。

Note

除了 delete,另一種釋放記憶體的方式就是將變數指向到 null

family.father = null
family.mother.husband = null

null 不會站用記憶體空間嗎?其實不會,因為 null 本身就是 JavaScript 在 Runtime 時就會存在的值,因此不會被 GC 清除,所以不論是幾個變數指向到 null ,其實 null 都是同一個記憶體空間,因此不會造成記憶體浪費。

Memory leak

Memory leak is a type of resource leak that occurs when a computer program incorrectly manages memory allocations in a way that memory which is no longer needed is not released.

Memory leak 是指已經用不到的 data,仍存在記憶體中沒被釋放

瀏覽器的記憶體是有限的,若記憶體不夠用,最壞的狀況會導致系統變慢或者是閃退。以前端角度看,新增 variables、執行 functions 都會佔用記憶體空間,正常情況若一直有用該記憶體中存的東西,那就沒有問題,但若程式沒寫好發生 Memory leak 就有可能導致記憶體被佔滿而發生卡頓。

以下是一些常導致 Memory leak 的原因

Global variables 全域變數

前面有提到 Global variables 就是 roots,也就是永遠不會被 GC 清掉

user = getUser();
var secondUser = getUser();
function getUser() {
return 'user';
}

function getExampleOne() {
a = "this variable will become global";
}

function getExampleTwo() {
this.b = "this variable will also become global";
}

getExampleOne()
getExampleTwo()

user, secondUser,a,b 都是 Global variables 也就是不會被清掉,若這些變數都不會再用到那也就發生了 Memory leak

Forgotten callbacks 事件監聽沒有移除

寫前端常需要監聽事件,若忘記清除事件,就會發生 Memory leak

const longString = new Array(100000).join('x');

document.addEventListener('keyup', function() {
doSomething(longString); //
});

longString 會一直在記憶體中而造成 Memory leak,建議改成不再需要用到就要 remove

const handleKeyUp = () => {doSomething(longString);}

// react 裡的寫法
useEffect(() => {
document.addEventListener('keyup', handleKeyUp);

// cleanup here
return () => {
document.removeEventListener('keyup', handleKeyUp);
}
}, [])

當然也可以用 AbortController,放棄監聽也可以解決 Memory leak 問題。

Forgotten timers 計時器未清除

const intervalId = setInterval(function() {
// everything used in here can't be collected
// until the interval is cleared
doSomething(object);
}, 2000);

以上程式碼兩秒就會跑一下所以 doSomething 永遠都會存在,但通常比較少一直需要執行它的專案,一旦不再使用記得 clearInterval(intervalId);

其他

  • Closure 閉包: 一般使用沒什麼問題,但因為 Closure 有 remembers its lexical scope” 特性,可能造成變數長存在記憶體中,所以小心不要使用過量
  • Map 與 Set 的使用: 即使某個被存入的值,在其他地方已經沒有被引用,該值仍會存在於 Set 或 Map 當中,不會被垃圾回收 (多數情況不會有太大問題)。但若確保想要被回收可以用 WeakSet 或 WeakMap。
  • infinite loop: 這不用多說,程式碼上掛給你看。不過這比較偏 stack overflow 也就是短時間內有超量的記憶體導致堆疊無法負荷。

Debugging memory leaks

以下這個影片是 Google 的 HTTP 203 分享他們出現記憶體洩漏時,是如何具體透過開發者工具來找問題的

Reference

此篇主要是參考以下兩個 reference,它們都寫的超好的!

--

--

Hannah Lin
Hannah Lin

Written by Hannah Lin

A Front End Developer girl comes from Taiwan

No responses yet