JavaScript quản lý bộ nhớ tự động (Garbage Collected). Cách cấp phát khác với C/C++ (manual malloc/free) hoặc Java (heap cho object, stack cho primitive).
Mô hình bộ nhớ JS
1. Call Stack (Ngăn xếp gọi) – Cho Primitive & Reference
- Lưu trữ: Primitive values (number, string, boolean, null, undefined, symbol, bigint) và References (con trỏ) trỏ đến object trong Heap.
- Cấp phát: Tự động khi vào scope (function/block).
- Giải phóng: Tự động khi ra khỏi scope (pop stack frame).
- Kích thước nhỏ, cố định, truy xuất cực nhanh.
2. Heap (Đống) – Cho Object
- Lưu trữ: Object, Array, Function, Date, RegExp, Map, Set... – dữ liệu kích thước động, phức tạp.
- Cấp phát: Khi tạo object (
new,{},[],function() {}). - Giải phóng: Garbage Collector (GC) tự động dọn khi không còn reference nào trỏ tới (unreachable).
- Kích thước lớn, động, truy xuất gián tiếp qua reference.
Minh họa: Biến primitive vs Object
// Primitive: Lưu trực tiếp trên Stack
let x = 10; // Stack: x → 10
let y = x; // Stack: y → 10 (copy giá trị)
y = 20; // Stack: y → 20, x vẫn là 10
// Object: Lưu reference trên Stack, Object trên Heap
let obj1 = { value: 10 };
// Stack: obj1 → ref#123
// Heap: ref#123 → { value: 10 }
let obj2 = obj1; // Stack: obj2 → ref#123 (copy reference)
obj2.value = 20; // Heap: ref#123 → { value: 20 }
console.log(obj1.value); // 20! (cùng object)
Vòng đời bộ nhớ
- Allocate (Cấp phát): Khai báo biến, tạo object, gọi hàm.
- Use (Sử dụng): Đọc/ghi biến, property, element.
- Release (Giải phóng): Ra khỏi scope (stack), GC dọn heap (unreachable).
Garbage Collection (GC) trong JS
- Mark-and-Sweep (Đánh dấu & Quét): Từ root (global/window), đánh dấu tất cả object reachable. Object không đánh dấu → sweep (dọn).
- Generational GC: Tách young generation (mới, chết nhanh) và old generation (lâu sống) – tối ưu hiệu năng (V8 Orinoco).
- Incremental/Concurrent GC: Chạy song song với JS thread, không block lâu.
Memory Leak (Rò rỉ bộ nhớ) thường gặp
// 1. Global variable vô tình
function leak() { accidentalGlobal = "leak"; } // window.accidentalGlobal
// 2. Closure giữ reference không cần
function createLeak() {
const bigData = new Array(1000000).fill("x");
return function() { return bigData.length; }; // bigData không bao giờ GC
}
// 3. Event listener không remove
element.addEventListener('click', handler);
// element removed from DOM but handler still references it
// 4. Timer không clear
setInterval(() => { ... }, 1000); // Quên clearInterval
// 5. Cache không giới hạn
const cache = {};
function get(key) {
if (!cache[key]) cache[key] = expensiveCompute(key);
return cache[key]; // Cache tăng vô hạn
}
Best Practice
- Dùng
const/letblock scope – biến sớm hết scope → sớm GC. - Nullify reference khi không dùng:
bigObj = null. - Remove event listener:
element.removeEventListener('click', handler). - Clear timer:
clearInterval(id),clearTimeout(id). - Dùng
WeakMap/WeakSetcho cache/metadata – không ngăn GC key. - DevTools Memory tab: Heap snapshot, Allocation timeline để profile leak.
Tóm tắt
- Primitive → Stack (giá trị). Object → Stack (reference) + Heap (object).
- Stack: tự động cấp phát/giải phóng theo scope (LIFO).
- Heap: GC tự động dọn object unreachable (Mark-and-Sweep).
- Memory leak = reference vô tình giữ object không dùng nữa.