Skip to content
Development
Skill

/javascriptcore-garbage-collector

JSC GC reference for Bun. Use for use-after-free, JS object leaks, "collected too early", or when touching WriteBarrier, visitChildren, visitAdditionalChildren, JSRef, JSC::Strong/Weak, hasPendingActivity, ensureStillAlive, addOpaqueRoot, reportExtraMemoryAllocated, IsoSubspace,

From plugin
bun
97k8 skills5 commands

Claude Code

$ npx -y skills add oven-sh/bun --skill javascriptcore-garbage-collector --agent claude-code

SKILL.md

javascriptcore-garbage-collector.SKILL.md
name: javascriptcore-garbage-collector
description: JSC GC reference for Bun. Use for use-after-free, JS object leaks, "collected too early", or when touching WriteBarrier, visitChildren, visitAdditionalChildren, JSRef, JSC::Strong/Weak, hasPendingActivity, ensureStillAlive, addOpaqueRoot, reportExtraMemoryAllocated, IsoSubspace, HeapAnalyzer, finalize.

JavaScriptCore's Garbage Collector (Riptide)

Riptide is **non-moving, generational, parallel, mostly-concurrent, conservative-on-the-stack**. Understanding those five words prevents most GC bugs in Bun.

The mental model

The heap is a graph. GC does a breadth-first search from **roots** → marks everything it reaches → everything unmarked is freed (lazily, on next allocation from that block). It does NOT compact or move objects — pointers stay stable for an object's lifetime.

**Two collection modes:**

  • **Eden GC**: only scans newly-allocated objects + remembered set. Fast, frequent.
  • **Full GC**: scans everything. Slower, rarer.

**It runs concurrently.** Marking happens on background threads _while JS is executing_; the mutator only stops at brief safepoints. `visitChildren` runs **off the main thread, racing with your code**.

How the VM gathers roots

Roots are not a hardcoded list — they are **marking constraints** registered with `Heap::addMarkingConstraint()` and run to fixpoint. The built-in set lives in `Heap::addCoreConstraints()` (`vendor/WebKit/Source/JavaScriptCore/heap/Heap.cpp:2970`):

| Tag | Name | What it marks | | ----- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Cs` | Conservative Scan | Native stack + registers of every JS thread, scanned word-by-word (`gatherStackRoots` → `ConservativeRoots`). Also JIT stub routines. World is stopped for this. | | `Msr` | Misc Small Roots | `vm.smallStrings`, `m_protectedValues` (`JSValueProtect`/`gcProtect`), `MarkedArgumentBuffer` lists, `vm.exception()` / `lastException()` / `m_terminationException` | | `Sh` | Strong Handles | `m_handleSet.visitStrongHandles()` — every `JSC::Strong<T>`. Also `vm().visitAggregate()` (atom string tables etc.) | | `D` | Debugger | Sampling profiler, type profiler, ShadowChicken | | `Ws` | Weak Sets | Iterates every `WeakBlock`; calls `WeakHandleOwner::isReachableFromOpaqueRoots()` to decide whether a weak ref should _become_ strong this cycle | | `O` | Output | Calls `visitOutputConstraints()` on already-marked cells in output-constraint subspaces (executables, WeakMaps). This is the "re-run after marking discovers more" hook | | `Jw` | JIT Worklist | CodeBlocks queued for compilation | | `Cb` | CodeBlocks | Executing/compiling CodeBlocks |

Bun registers an additional constraint, `DOMGCOutputConstraint` (`src/jsc/bindings/BunGCOutputConstraint.cpp`), which calls `visitOutputConstraints` on every marked cell in Bun's output-constraint subspaces (event targets, generated classes with `visitAdditionalChildren`, etc.).

**Constraint volatility** controls when they re-run during the fixpoint:

  • `GreyedByExecution` — may produce new grey cells whenever the mutator runs (re-run after every resume)
  • `GreyedByMarking` — may produce new grey cells when _other_ marking happens (re-run after each drain)
  • `SeldomGreyed` — usually doesn't add anything; run last

Object layout: the 8-byte JSCell header

Every GC-managed object inherits `JSCell` (`runtime/JSCell.h`):

| StructureID (4) | indexingTypeAndMisc (1) | JSType (1) | flags (1) | cellState (1) |
  • `StructureID` — compressed hidden-class pointer
  • `indexingTypeAndMisc` — 2 bits are an embedded `WTF::Lock` (the **cell lock**); always CAS this byte
  • `cellState` — inlined GC color, used by the write barrier

Out-of-line, in the `MarkedBlock` footer (or `PreciseAllocation` header for objects >~8KB):

  • `isMarked` bit — survived last GC
  • `isNewlyAllocated` bit — allocated since last GC

Liveness = `isMarked || isNewlyAllocated` (with logical-versioning so blocks aren't swept eagerly).

CellState and the write barrier

`vendor/WebKit/Source/JavaScriptCore/heap/CellState.h`:

PossiblyBlack   = 0   // visited (or old-space-pending-rescan during full GC)
DefinitelyWhite = 1   // new / unmarked
PossiblyGrey    = 2   // on the mark stack

Generational + concurrent GC share **one** retreating-wavefront barrier:

// After: obj->field = newValue
if (obj->cellState <= blackThreshold)   // 0 normally, bumped while GC is marking
    writeBarrierSlowPath(obj);          // → put obj on remembered set / revisit

**You almost never write this by hand.** Use `WriteBarrier<T>` as the field type and call `.set(vm, owner, value)` — it stores then barriers. A raw `JSCell*` / `JSValue` member without a `WriteBarrier` wrapper is a bug: eden GC will free the target out from under you.

`LazyProperty<Owner, T>`, `LazyClassStructure`, and `WriteBarrierStructureID` are barrier-aware variants for lazily-initialized fields and structures.

Allocation: where objects live

`bmalloc/libpas` provides pages; JSC carves them up:

  • **`MarkedBlock`** — 16KB block, fixed cell size (segregated free list). Footer holds bitvectors. 16-byte minimum cell al
Read more
@oven-sh
Ships withbun

Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one

Stars on GitHub

Momentum

stars in total
96.7k
Star history of oven-sh/bun on GitHub, a chart by star-history.com

Other skills on bun.

implementing-jsc-classes-cpp

implementing-jsc-class…

Implements JavaScript classes in C++ using JavaScriptCore. Use when creating new JS classes with C++ bindings, prototypes, or…

@oven-sh@oven-shInstall Skill
implementing-jsc-classes-rust

implementing-jsc-class…

Creates JavaScript classes using Bun's bindings generator (.bind.ts). Use when implementing new JS APIs in Rust with JSC…

@oven-sh@oven-shInstall Skill
rust-system-calls

rust-system-calls

Guides using bun_sys for system calls and file I/O in Rust. Use when implementing file operations, opening fds, or any syscall…

@oven-sh@oven-shInstall Skill
slowest-tests

slowest-tests

Find the top-N slowest test files in CI from a recent BuildKite run, optionally posting the results to a Slack channel as a…

@oven-sh@oven-shInstall Skill
verify

verify

Verify a Bun runtime change by driving the debug binary end-to-end.

@oven-sh@oven-shInstall Skill
writing-bundler-tests

writing-bundler-tests

Guides writing bundler tests using itBundled/expectBundled in test/bundler/. Use when creating or modifying bundler, transpiler,…

@oven-sh@oven-shInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills10d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc12d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react1d252k
@facebook@facebookInstall Skill

Questions about javascriptcore-garbage-collector.

Which coding agents can use javascriptcore-garbage-collector?
javascriptcore-garbage-collector installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.