The Dart VM uses a generational garbage collector with two generations and both parallel and concurrent collection phases.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/dart-lang/sdk/llms.txt
Use this file to discover all available pages before exploring further.
GC Architecture
The GC consists of:- New generation - Collected by parallel, stop-the-world semispace scavenger
- Old generation - Collected by concurrent-mark concurrent-sweep or concurrent-mark parallel-compact
Object Representation for GC
Pointer Tagging
Object pointers are distinguished by tag bits:| Pointer Value | Referent |
|---|---|
0x00000002 | Small integer 1 (Smi) |
0xFFFFFFFE | Small integer -1 (Smi) |
0x00A00001 | Heap object at 0x00A00000, in old-space |
0x00B00005 | Heap object at 0x00B00004, in new-space |
- Smis have tag
0(LSB = 0) - Heap objects have tag
1(LSB = 1)
Generation Identification
Heap objects are allocated with specific alignment:- Old-space - Double-word aligned (
address % double-word == 0) - New-space - Offset from double-word alignment (
address % double-word == word)
Safepoints
A mutator is any thread that can allocate, read, or write to the heap. Some GC phases require mutators to stop accessing the heap - these are safepoint operations:- Marking roots at the start of concurrent marking
- The entirety of a scavenge
- Installing optimized code
- Mutators stop accessing the heap
- No pointers into the heap held (except via handles)
- Runtime code holds only handles, not
ObjectPtrorUntaggedObject
Thread States
Thread state is tracked viaThread::ExecutionState:
| State | At Safepoint? | Description |
|---|---|---|
kThreadInNative | Yes | Executing external native code |
kThreadInBlockedState | Yes | Blocked on a lock |
kThreadInVM | No | Executing C++ VM code |
kThreadInGenerated | No | Executing compiled Dart code |
Scavenge (New Generation GC)
The new generation is collected using Cheney’s semispace algorithm.Parallel Scavenge
By default, 2 scavenger tasks run on separate threads (FLAG_scavenger_tasks):
- Workers compete to process the root set (including remembered set)
- When a worker copies an object to to-space:
- Allocates from worker-local bump region
- Same worker processes the copied object
- When promoting to old-space:
- Allocates from worker-local freelist
- Promoted object added to work-stealing queue
- Workers use compare-and-swap to install forwarding pointers
- Loser un-allocates and uses winner’s object
Configuration Flags
Mark-Sweep (Old Generation GC)
Marking Phase
All objects start with the mark bit clear in their header.- Visit each root pointer
- If target’s mark bit is clear:
- Set mark bit
- Add target to marking stack (grey set)
- Remove objects from marking stack and mark their children
- Repeat until marking stack is empty
Sweeping Phase
Visit each object:- If mark bit is clear → Add memory to free list for future allocations
- Otherwise → Clear mark bit for next cycle
- If entire page is unreachable → Release to OS
Concurrent Marking
To reduce pause times, marking runs concurrently with the mutator.Write Barrier
With concurrent marking, the mutator could write a pointer to an unmarked object (TARGET) into an already-marked object (SOURCE), causing incorrect collection. The write barrier prevents this:Data Race Safety
Operations use relaxed ordering (no synchronization):- Concurrent marker starts with acquire-release (sees all prior mutator writes)
- Old-space objects created before marking: Marker may see old or new slot values
- Both are valid pointers, so no corruption
- May lose precision (retain dead object) but remain correct
- Old-space objects created after marking: Allocated black (marked) so marker won’t visit
- New-space objects in active TLAB: Visited only during safepoint
- New-space outside TLAB: Synchronized by store-release when switching TLABs
Configuration Flags
Mark-Compact
The VM includes a sliding compactor for old generation:- Compact representation: Heap divided into blocks
- Each block records target address and surviving double-word bitvector
- Constant-time access by masking object address to get page header
Write Barrier Elimination
The compiler eliminates write barrier checks when provable:Generational Barrier
Checks ifcontainer is old and not remembered, and value is new.
Incremental Barrier
Checks ifvalue is not marked and marking is in progress.
Elimination Cases
Barrier eliminated when:valueis a constant (always old and marked via constant pools)valuehas static typebool(all values are constants: null, false, true)valueis known to be a Smi (not a heap object)valueiscontainer(self-references can’t cross generations/marking states)containeris newly allocated and:- No GC between allocation and store, OR
- No new Dart frames between allocation and store
Weakness
The GC supports various weak reference types:Heap Objects
- WeakReference - Weakly references a single object
- WeakProperty - Ephemeron: if key is reachable, value is reachable
- WeakArray - Weakly references variable number of objects
- Finalizers - Weak reference + callback when collected
Non-Heap Objects
- Dart_WeakPersistentHandle / Dart_FinalizableHandle - C API weak references
- Object ID ring - VM service object IDs (strong for minor GC, weak for major GC)
- Weak tables - Weak association of objects to integers
Weak Processing
- GC traces strong references, collecting encountered weak objects
- When strong worklist is empty, examine WeakProperties for reachable keys
- Add corresponding values to worklist
- Repeat until worklist empty
Finalizers
Two finalizer-aware object types:- FinalizerEntry - Contains value, detach key, token, finalizer reference, external size
- Finalizer - Contains all entries, collected entries list, isolate reference
- Entry moved to collected list
- Message sent to invoke callback
- Native finalizers: callback invoked immediately in GC
- Tasks use atomic exchange on collected list head
- Mutators stopped during entry processing
- Dart code uses atomic exchange to get collected entries
Become
Become atomically forwards object identities:- Heap walk replaces every pointer to “before” object with “after” object
- After object gains identity hash of before object
- Used during hot reload to map old program/instances to new
GC Debugging and Tuning Flags
Key Source Files
runtime/vm/heap/scavenger.h- Parallel scavenger implementationruntime/vm/heap/marker.h- Concurrent markingruntime/vm/heap/sweeper.h- Concurrent sweepingruntime/vm/heap/compactor.h- Parallel compactionruntime/vm/heap/become.h- Become implementationruntime/vm/heap/safepoint.h- Safepoint coordinationruntime/vm/heap/freelist.h- Free list managementruntime/docs/gc.md- Detailed GC documentation