cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

The heap & pointer compression

Learn how V8 organizes heap spaces and pages, allocates with bump pointers, compresses heap pointers, and reports Node heap limits.

By the end, you can
  • 01
    Name the main heap spacesExplain why V8 separates young objects, old objects, code, and very large objects instead of keeping one giant bag of memory.
  • 02
    Model page allocationStep through a bump allocator that fills one regular page and moves to the next when the object no longer fits.
  • 03
    Explain compressed pointers safelyDescribe the 4 GB pointer-compression cage, why official Node builds differ from Chrome, and how the V8 sandbox limits damage.

The heap is organized space

The beginner picture says JavaScript values live on a stack or a heap. That is useful, and the lessons on stack and heap and memory management explain that level. This lesson zooms into V8's heap: the managed memory area where objects, strings, arrays, functions, metadata, and code objects are placed.

Definition

A heap layout is an engine's internal arrangement of managed memory into spaces and pages. In V8, most small objects are placed on regular pages inside spaces such as new space, old space, and code space, while very large objects get large-object allocations. Pointer compression is V8's technique for storing in-cage heap pointers as 32-bit offsets.

We will not re-teach value tagging or Smis here; the value-representation lesson owns that. We also will not teach garbage collection algorithms here; the upcoming garbage-collection lessons, including gc-basics, own collection. Here the question is placement: where does the engine put things, and why does V8 sometimes store half-sized heap pointers?

This lesson follows Prototypes inside the engine in the same batch. After heap layout, Stack frames & calling conventions moves from managed heap memory to the machine stack used during calls.

New, old, code, and large-object spaces

A space is a region of V8's heap with one job. Fresh small objects normally begin in a young area called new space. Objects that survive long enough can end up in old space. Executable code objects live in code spaces because they have different permissions and accounting. Objects too large for regular pages go through large-object spaces.

Node-only heap space probeJavaScript
const v8 = require("node:v8");const names = v8.getHeapSpaceStatistics().map((space) => space.space_name).sort();console.log(names.join(", "));
Verified runtime fact

The lesson test runs this probe in Node 22 on macOS and Linux-style CI. It asserts stable V8 12.4 space names such as new_space, old_space, code_space, large_object_space, new_large_object_space, and code_large_object_space. It does not assert exact byte counts.

V8 heap spaces you should recognize
SpaceTypical contentsWhy the split exists
new_spaceFresh, small objectsCheap bump allocation and short-lived values. Collection details belong to the garbage-collection lessons.
old_spaceObjects that survived long enough to be promotedLonger-lived ordinary objects and metadata.
code_spaceExecutable code objectsJIT-produced code and related code objects need different permissions and accounting.
large_object_spaceObjects bigger than a regular-page object limitLarge objects get dedicated large-object pages instead of being squeezed into normal pages.
new_large_object_space and code large spacesYoung or code objects that are already largeNode's space list exposes separate large-object spaces for these cases.
Read-only, trusted, and shared spacesEngine-managed objects with special sharing or trust rulesThey are visible in Node's statistics, but not where everyday app objects usually start.

The exact list changes as V8 evolves. For example, this Node build also exposes read-only, trusted, shared, and corresponding large-object spaces. Treat those names as V8 implementation details, not ECMAScript requirements.

Pages and bump allocation

A page is a fixed-size chunk inside a paged heap space. Current V8 source keeps the regular page size at 256 KB for the common configuration: kPageSizeBits = 18, so 1 << 18 bytes. A regular object can be up to half that size in the source constant used by the lesson, so roughly 128 KB.

V8 source constants behind the page modelText
// V8 source, src/base/build_config.h:// constexpr int kPageSizeBits = 18;// constexpr int kRegularPageSize = 1 << kPageSizeBits;// V8 source, src/common/globals.h:// constexpr int kMaxRegularHeapObjectSize = (1 << (kPageSizeBits - 1));

The fast allocation path is often a bump allocation: keep a pointer named something like nextFree, return the address at nextFree, and move nextFree forward by the object size. If the object does not fit, the allocator moves to a new page or a slower path.

Real-life analogyA parking lot fills from the first free spot

A parking lot fills from the first free spot. No one searches the whole lot for each car; the marker moves forward after each one parks.

In real life: The first free spot is known
In JavaScript: The allocator keeps a nextFree pointer
In real life: A car takes that spot
In JavaScript: A small object gets the current address
In real life: The marker moves forward
In JavaScript: nextFree bumps by the aligned object size
In real life: A full row opens another row
In JavaScript: A full page sends allocation to another page

Where the analogy stops: Real heaps also have free lists, alignment rules, evacuation, and collection. This only shows the fast path for fresh space.

Step through bump allocation across pages
Step 0 of 11Ready
Your turn: follow the blue line

Watch a bump allocator fill a tiny page from left to right, then switch to a fresh page when the next object does not fit.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
let page = 0;let nextFree = 0; function align16(bytes) {  return Math.ceil(bytes / 16) * 16;} function allocate(label, requested) {  const size = align16(requested);  if (nextFree + size > pageSize) {    page += 1;    nextFree = 0;  }  const address = { page, start: nextFree, end: nextFree + size };  nextFree += size;  return label + ": page " + address.page + ", bytes " + address.start + "-" + address.end;} console.log(allocate("header", 120));console.log(allocate("items", 380));console.log(allocate("names", 460));console.log(allocate("avatar", 180));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Line 16 in the replay is the heart of the fast path: nextFree += size. Line 12 appears only when the next object crosses the page boundary. This is a teaching model; it does not inspect your browser's actual heap.

Pages and allocation terms
TermMeaningWhy it matters
Regular V8 pageUsually 1 << 18 bytes, or 256 KB, in current V8 sourceSmall and medium objects can share a page.
Maximum regular objectCurrent source defines it as half a regular page, about 128 KBBigger objects go to a large-object space.
Bump pointerA nextFree address that moves forward after each allocationFast path: check room, return old nextFree, then bump.
Large-object pageOne big allocation gets its own page-like regionAvoids copying or fitting huge payloads into regular pages.

Why very large objects get their own space

Large allocations are awkward inside regular pages. If a backing store is bigger than the maximum regular object size, it cannot fit in the normal small-object rhythm. V8's large-object spaces handle those allocations separately. The object is not automatically a leak; it is just too large for the ordinary page path.

Real-life analogyBig furniture takes the stairs

A lift carries small items, while big furniture goes by the stairs. The route depends on size, not on whether the furniture is wanted.

In real life: Small items fit in the lift
In JavaScript: Small objects fit regular pages
In real life: A sofa does not fit
In JavaScript: A huge backing store would waste or overflow a page
In real life: Big furniture uses the stairs
In JavaScript: V8 uses a large-object space
In real life: The furniture is still yours
In JavaScript: References still determine whether the object is live

Where the analogy stops: The stairs do not explain collection policy. The garbage-collection module covers when unreachable large objects are reclaimed.

Node-only large-object space probeJavaScript
const v8 = require("node:v8");if (typeof global.gc !== "function") throw new Error("run with --expose-gc");function used(spaceName) {  return v8.getHeapSpaceStatistics().find((space) => space.space_name === spaceName)?.space_used_size ?? 0;}global.gc();const before = used("large_object_space");global.big = new Array(20000).fill(1);global.gc();const after = used("large_object_space");console.log(after > before);console.log(after - before >= 131072);
Verified runtime fact

The test runs this with --expose-gc, keeps a 20,000-element array alive, and asserts that large_object_space grows by at least the 128 KB regular-object threshold. The test checks the relation, not an exact byte total.

Which space is the best fit?
  • A tiny { name: "Asha" } object just allocated inside a click handler.
  • A configuration object kept by the app for the whole session.
  • Machine code generated for a hot JavaScript function.
  • An array backing store bigger than roughly 128 KB in current V8.
  • A small closure allocated for one validation pass and then dropped.
  • A very large generated code object.
Try it yourself
0 of 6 correct

Sort each object or payload by the V8 space family it most likely belongs to in this lesson's model.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Pointer compression and the 4 GB cage

On a normal 64-bit machine, a pointer takes eight bytes. V8's pointer-compression blog explains the saving: if every V8 heap object pointer is inside one reserved 4 GB region, memory can store a 32-bit offset instead of the full address. Generated code adds the cage base back when it needs the real address.

Step through pointer compression
Step 0 of 7Ready
Your turn: follow the blue line

Follow a BigInt teaching model for V8 pointer compression: full address, 32-bit offset, then base plus offset again.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const cageSize = 0x0000000100000000n; function compressPointer(address) {  if (address < cageBase || address >= cageBase + cageSize) {    throw new RangeError("address is outside the 4 GB cage");  }  return Number(address - cageBase);} function decompressPointer(offset) {  return cageBase + BigInt(offset >>> 0);} const address = cageBase + 0x12345678n;const compressed = compressPointer(address);console.log("0x" + compressed.toString(16).padStart(8, "0"));console.log("0x" + decompressPointer(compressed).toString(16));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Playground: compress an address inside the cage
Teaching model for one compressed pointerJavaScript
const cageBase = 0x0000000400000000n;const cageSize = 0x0000000100000000n; function compressPointer(address) {  if (address < cageBase || address >= cageBase + cageSize) {    throw new RangeError("outside the cage");  }  return Number(address - cageBase);}
Compression resultinside cage
cage base0x400000000
cage last byte0x4ffffffff
full address0x412345678
compressed offset0x12345678
decompressed0x412345678
Try it yourself

0x412345678 is inside the teaching cage. The compressed form is 0x12345678, and base + offset decompresses to 0x412345678.

This is a BigInt teaching model. It does not inspect your browser's engine, and it parses only hexadecimal or decimal addresses.
Browser-safe pointer-compression modelPop out in the code editor (opens in a new tab)JavaScript
const cageBase = 0x0000000400000000n;const cageSize = 0x0000000100000000n; function compressPointer(address) {  if (address < cageBase || address >= cageBase + cageSize) {    throw new RangeError("address is outside the 4 GB cage");  }  return Number(address - cageBase);} function decompressPointer(offset) {  return cageBase + BigInt(offset >>> 0);} const address = cageBase + 0x12345678n;const compressed = compressPointer(address);console.log("0x" + compressed.toString(16).padStart(8, "0"));console.log("0x" + decompressPointer(compressed).toString(16));

Line 5 is the safety check in the model: an address outside the 4 GB cage cannot be represented by this compressed pointer. Line 8 stores the 32-bit offset. Line 12 decompresses by adding the cage base back. V8's real representation also interacts with tagged values and Smis; the neighboring value-representation lesson owns those details.

Full pointers, compressed pointers, and build differences
IdeaWhat it storesImportant caveat
Full heap pointerA machine address wide enough for the processEight bytes on a normal 64-bit build.
Compressed heap pointerA 32-bit offset inside one reserved cageFour bytes in memory, decompressed as cage base plus offset before use.
The 4 GB cageThe address range that 32 bits can nameIt limits where compressed heap objects can live, not how every Node process must be configured.
Official Node 22 buildsPointer compression and sandbox flags are 0 in process.config.variablesNode can keep larger heaps than Chrome-style compressed-pointer builds.
Chrome on 64-bitV8's public posts describe pointer compression and the sandbox as Chrome featuresChrome and Node are both V8 embedders, but their build flags differ.
Node-only Smi range probeJavaScript
console.log(%IsSmi(2 ** 31 - 1));console.log(%IsSmi(2 ** 31));

This official Node build does not enable pointer compression, so a native V8 probe confirms that 2 ** 31 - 1 is still a Smi while 2 ** 31 is not. Chrome's pointer-compressed 64-bit V8 uses 31-bit Smi payloads, as the V8 pointer-compression article describes. The value-representation lesson owns the full tagging story.

The V8 sandbox

Pointer compression saves memory. The V8 sandbox is a security design. The V8 sandbox blog describes isolating V8 heap memory so a V8 heap corruption bug cannot directly reach the rest of the host process. In the software design, in-sandbox pointers are offsets, and references to memory outside the sandbox go through tables such as an external pointer table.

Chrome and Node differ

The V8 blog describes pointer compression and the sandbox as Chrome 64-bit features. Official Node 22 builds used for this lesson report process.config.variables.v8_enable_pointer_compression === 0 and process.config.variables.v8_enable_sandbox === 0. Node embeds V8 with different build choices.

Node-only build-flag probeJavaScript
console.log(process.config.variables.v8_enable_pointer_compression);console.log(process.config.variables.v8_enable_sandbox);

The sandbox is not a replacement for the browser process sandbox, and it is not a promise that every bug becomes harmless. It is a defense layer: corrupted in-cage data should not be enough to read or write arbitrary process memory without a sandbox bypass.

Heap size limits

v8.getHeapStatistics().heap_size_limit reports V8's managed-heap ceiling for a Node process. It is not the same as your operating system's resident memory, and it is not an exact prediction of when a real app will fail. Still, it is useful evidence when you change a V8 heap-size flag.

Heap limit relation, not exact bytesShell
node --max-old-space-size=64 -e "console.log(require('node:v8').getHeapStatistics().heap_size_limit)"node --max-old-space-size=256 -e "console.log(require('node:v8').getHeapStatistics().heap_size_limit)"
Verified runtime fact

The test launches two child Node processes and asserts that --max-old-space-size=256 reports a larger heap_size_limit than --max-old-space-size=64. Exact numbers vary, so the lesson never pins them.

If a Node process runs out of heap, increasing the limit can buy time, but it does not explain why memory grows. Use heap snapshots, allocation profiles, and application knowledge to find retained data.

What developers should do

Most frontend work should not depend on V8 page sizes or build flags. Use this knowledge to interpret measurements. A giant array, image buffer, or parsed JSON payload may appear in large-object accounting. A short-lived stream of small objects may churn in new space. A retained cache can grow old space. Each clue suggests a measurement to run, not folklore to apply blindly.

  • Use heap snapshots to identify what is retained, then fix ownership or cleanup.
  • Measure before rewriting data structures for a page-size or pointer-compression guess.
  • Avoid keeping huge arrays or strings reachable after the UI no longer needs them.
  • For Node services, treat --max-old-space-size as a capacity knob, not a leak fix.

If the issue is collection behavior, continue in the garbage-collection module. If the issue is call memory, continue to stack frames next.

Common misconceptions

  • “The JavaScript spec says objects are in new space or old space.” No. Spaces are V8 implementation details.
  • “Pointer compression means Node always has a 4 GB heap.” Official Node builds here do not enable pointer compression.
  • “A large-object allocation is a leak.” It is only a placement choice. A leak means unwanted reachability over time.
  • “The sandbox and garbage collector do the same job.” The sandbox limits exploit reach; the collector reclaims unreachable objects.
  • “Heap limit equals total process memory.” It covers V8 managed heap, not every native allocation or OS page.
Ideas that are easy to confuse
IdeaPrecise meaningDo not confuse it with
JavaScript semanticsObjects behave according to the ECMAScript specHeap spaces, pages, and cages are V8 strategies, not language guarantees.
Pointer compressionStores in-cage heap pointers as 32-bit offsetsIt is not the same thing as the V8 sandbox or garbage collection.
Heap limitA configured ceiling for V8's managed heapIt is not the exact resident memory your operating system shows.
Large-object spaceA placement choice for large allocationsIt does not mean the object is leaked or immortal.

Practice exercises

Exercise 1 · Warm-upPredict the page switch

In the bump-allocation replay, what page does avatar land on?

Starter codePop out in the code editor (opens in a new tab)JavaScript
// The first page has only 48 bytes left.
// The next aligned allocation needs 192 bytes.

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeWrite the offset calculation

    Use the pointer-compression model to compute the compressed form of base + 0x20n.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const base = 0x400000000n;
    const address = base + 0x20n;
    // Print the compressed offset as eight hex digits.

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind the cage-boundary bug

      A learner tries to compress exactly base + 4 GB. Is that address inside or outside the cage?

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeReason about a heap flag

        If you raise --max-old-space-size, should the reported heap limit be higher or lower?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeApply it to a real app

          Your dashboard grows old-space memory after every route change. What should you do before rewriting data structures?

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · PracticeRemember the Node build flags

            What values does official Node 22 report for v8_enable_pointer_compression and v8_enable_sandbox in this lesson?

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              Heap layout quiz · 8 questionsScore: first tries count
              1. Question 1 of 8Why does V8 split the heap into spaces?

                Choose an answer to see the explanation.

              2. Question 2 of 8What does this bump-allocation snippet print last?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const pageSize = 1024;
                let page = 0;
                let nextFree = 976;
                const size = 192;
                if (nextFree + size > pageSize) {
                  page += 1;
                  nextFree = 0;
                }
                console.log(page);

                Choose an answer to see the explanation.

              3. Question 3 of 8What does this pointer-compression model print first?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const base = 0x400000000n;
                const address = base + 0x12345678n;
                const compressed = Number(address - base);
                console.log("0x" + compressed.toString(16).padStart(8, "0"));

                Choose an answer to see the explanation.

              4. Question 4 of 8Which statement about official Node 22 builds and Chrome is correct for this lesson?

                Choose an answer to see the explanation.

              5. Question 5 of 8Why can a large object skip regular page bump allocation?

                Choose an answer to see the explanation.

              6. Question 6 of 8What does raising --max-old-space-size do to v8.getHeapStatistics().heap_size_limit?

                Choose an answer to see the explanation.

              7. Question 7 of 8What is the V8 sandbox trying to prevent?

                Choose an answer to see the explanation.

              8. Question 8 of 8Which production habit follows from this lesson?

                Choose an answer to see the explanation.

              Key takeaways

              • V8 splits managed heap memory into spaces because fresh objects, old objects, code, and huge objects need different handling.
              • Regular pages are 256 KB in current V8 source for the common configuration; the maximum regular object size is half a page.
              • Bump allocation is the fast path: return the current next-free pointer, then move it forward if the object fits.
              • Pointer compression stores 32-bit offsets inside a 4 GB cage; official Node builds here differ from Chrome and report the feature disabled.
              • The V8 sandbox is a security layer that keeps heap corruption from directly reaching the rest of the process.

              Remember the one-liner.
              V8 makes the heap fast and compact by grouping objects into purpose-built spaces, allocating regular objects page by page, and, in Chrome, storing heap pointers as in-cage offsets.

              Up next: Stack frames & calling conventions, where you will see what a function call puts on the machine stack.

              CompleteFrontend Clear concepts. Working examples.