Mastering JavaScript Memory: Types, Garbage Collection, and Leak Prevention

This article explains JavaScript's memory allocation across code, stack, and heap spaces, details how different data types are stored, describes garbage collection mechanisms, and provides practical steps to detect and fix memory leaks, including a React-specific example.

ELab Team
ELab Team
ELab Team
Mastering JavaScript Memory: Types, Garbage Collection, and Leak Prevention

Preface

With the development of the web, front‑end pages are now loaded not only in browsers but also in app WebViews. As device performance improves, front‑end pages play an important role in apps, leading to longer page lifetimes and a need to pay more attention to memory management to prevent leaks and improve performance.

To build high‑performance front‑end applications and avoid crashes, it is essential to understand JavaScript’s memory allocation and reclamation mechanisms.

JavaScript Data Storage Mechanism

Memory Spaces

During JavaScript execution there are three main memory spaces: code space, stack space, and heap space.

Code space : stores executable code.

Stack space : a contiguous memory region with small capacity and fast access, organized as LIFO.

Heap space : a non‑contiguous region with larger capacity used for storing large data, slower to access.

Data Types

JavaScript has eight data types. The Object type is a reference type; the other seven are primitive types. Objects are key‑value structures composed of the primitive types.

Stack and Heap

The stack is the JavaScript call stack, storing execution contexts and small primitive values.

Variable environment stores variables declared with var and function declarations; lexical environment stores let and const variables.

The heap stores large data such as reference types; references to heap objects are kept in the stack, making heap access slower.

Because the stack must remain contiguous, it is kept relatively small; large objects are allocated in the heap.

Closures

Inner functions can always access variables declared in their outer functions; when an outer function returns an inner function, the inner function retains references to the outer variables, forming a closure.

Closure data is stored as an object in the heap.

All inner functions of a closure share the same closure object; any inner function that references an outer variable includes it in the closure.

Data Types Stored in Stack and Heap

Are primitive types stored in the stack and reference types in the heap? In V8, numbers are split into smi (small integers stored directly in the stack) and heapNumber (stored in the heap).

var times = 50000;
var smi_in_stack = 1;
var heap_number = 1.1;

// about 1.5~1.6ms, fast
console.time('smi_in_stack');
for (let i = 0; i < times; i++) {
  smi_in_stack++;
}
console.timeEnd('smi_in_stack');

// about 2.1~2.5ms, slow
console.time('heap_number');
for (let i = 0; i < times; i++) {
  heap_number++;
}
console.timeEnd('heap_number');

V8 also defines an oddball type (null, undefined, true, false).

function BasicType() {
  this.oddBall1 = true;
  this.oddBall2 = false;
  this.oddBall3 = undefined;
  this.oddBall4 = null;
  this.oddBall5 = '';
}
const obj1 = new BasicType();
const obj2 = new BasicType();

Oddball values and empty strings share a single heap allocation created at V8 startup, so their stack representation holds a reference to that heap value.

function Obj() {
  this.string = 'str';
  this.num1 = 1;
  this.num2 = 1.1;
  this.bigInt = BigInt('1');
  this.symbol = Symbol('1');
}
const obj = new Obj();
debugger;
obj.string = 'other str';
obj.num1 = 2;
obj.num2 = 1;
obj.bigInt = BigInt('2');
obj.symbol = Symbol('2');

After the debugger, the heap snapshots show that bigInt, string, and symbol addresses change because their size is dynamic, while heapNumber addresses stay the same.

Summary of primitive storage locations:

Type

Storage

Number

smi in stack, heapNumber in heap

String

heap

Boolean

heap

Null

heap

undefined

heap

BigInt

heap

Symbol

heap

JavaScript Memory Reclamation

Stack Memory Reclamation

function fn1() {
  //....
  function fn2() {
    //...
  }
  fn2();
}
fn1();

The call stack has a pointer (ESP) that moves as functions are entered and exited. When a function returns, its stack frame becomes invalid and will be overwritten by subsequent calls, so stack memory is automatically reclaimed.

Heap Memory Reclamation

Garbage collection in JavaScript follows the generational hypothesis: most objects die young, while long‑living objects survive longer.

Mark live objects and dead objects.

Collect memory occupied by dead objects.

Optional memory compaction to eliminate fragmentation.

Minor (Scavenge) Collector

The minor collector works on the young generation, dividing it into an object region and a free region. When the object region fills, live objects are copied to the free region, and the roles swap.

Major (Mark‑Compact) Collector

The major collector handles the old generation using a mark‑compact algorithm. It pauses the main thread (full stop‑the‑world) while moving live objects and freeing the rest. Incremental marking reduces pause time.

JavaScript Memory Leaks

Memory leaks usually occur in the heap because stack frames are reclaimed after function execution. Common leak sources include detached DOM nodes, lingering references, and strings.

Detecting Leaks

Build a production bundle without minification, with sourcemaps, and without console statements.

Run the app in a local server.

Continuously take heap snapshots and observe timeline changes.

Identify abnormal growth (e.g., memory rising from 22.5 MB to 34.6 MB, conversation instances increasing disproportionately).

Switch between conversations and watch for detached DOM nodes.

Focus on anchor objects, detached DOM, and strings when analyzing snapshots.

React‑specific Leak Example

When a component is unmounted but an asynchronous callback still calls setState, a reference is retained, causing a leak.

// test code
const [test, setTest] = useState(null);
useEffect(() => {
  (async () => {
    await sleep(3000);
    const obj = new TestObj();
    setTest(obj);
  })();
}, []);

Fix by tracking the mounted state and aborting updates after unmount.

const [test, setTest] = useState(null);
useEffect(() => {
  let unMounted = false;
  (async () => {
    await sleep(3000);
    if (unMounted) return;
    const obj = new TestObj();
    setTest(obj);
  })();
  return () => {
    unMounted = true;
  };
}, []);

In development mode React warns about such patterns; production builds do not retain the references.

Conclusion

The article explains where JavaScript types are stored, how stack and heap memory are reclaimed, and methods for detecting and troubleshooting memory leaks, including a React example. Effective monitoring and user‑driven data analysis are essential for mitigating leaks in complex applications.

References

https://developer.chrome.com/docs/devtools/memory-problems/memory-101/

https://www.cnblogs.com/goloving/p/15352261.html

https://hashnode.com/post/does-javascript-use-stack-or-heap-for-memory-allocation-or-both-cj5jl90xl01nh1twuv8ug0bjk

https://www.ditdot.hr/en/causes-of-memory-leaks-in-javascript-and-how-to-avoid-them

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

garbage-collectionmemory-managementmemory-leak
ELab Team
Written by

ELab Team

Sharing fresh technical insights

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.