Garbage Collection Internals
Why Dart's GC is Designed for UI
Dart's Garbage Collector is not a general-purpose GC — it is specifically optimized for UI workloads characterized by: many short-lived objects (one per frame), occasional long-lived objects (state, data models), and strict latency requirements (sub-16ms frame budget).
Flutter renders at 60fps or 120fps. That means the GC has a budget of ~8ms per frame to do ALL its work — including GC pauses. Dart's GC is engineered around this constraint.
Generational Hypothesis
The GC is built on the generational hypothesis: most objects die young. A widget rebuilt every frame creates many objects that are used once and discarded immediately. Collecting these frequently in a small space is much faster than doing one big GC pass.
Memory Layout:
┌──────────────────────────────────────────┐
│ NEW SPACE (~8MB) │ ← Young objects live here
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ From-Space │ │ To-Space │ │ ← Scavenging copies between them
│ └─────────────┘ └─────────────────┘ │
└──────────────────────────────────────────┘
┌──────────────────────────────────────────┐
│ OLD SPACE │ ← Long-lived objects
│ (Full GC: mark-sweep-compact) │ ← Runs less frequently
└──────────────────────────────────────────┘
Minor GC — Scavenging
The minor GC (scavenger) works on new space:
- Cheney's algorithm: Two semi-spaces (From and To)
- Live objects are copied from From-Space to To-Space
- Dead objects are simply abandoned — no deallocation needed
- Objects that survive N collections are promoted to old space
- Typical pause: ~1-2ms — well within the 16ms frame budget
Major GC — Mark-Sweep-Compact
Old space uses a tri-color mark-sweep collector:
- Mark phase: Traverses the object graph, marking reachable objects
- Sweep phase: Reclaims memory from unmarked (dead) objects
- Compact phase (optional): Moves live objects together to reduce fragmentation
- Uses incremental and concurrent techniques to reduce pause times
Writing GC-Friendly Dart Code
dart// BAD: Allocating objects on every build Widget build(BuildContext context) { return Container( decoration: BoxDecoration( // allocated every build borderRadius: BorderRadius.circular(8), // allocated every build color: Colors.blue, ), child: Text(label), ); } // GOOD: Cache constant objects as static fields class MyWidget extends StatelessWidget { static const _decoration = BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(8)), color: Colors.blue, ); Widget build(BuildContext context) { return Container(decoration: _decoration, child: Text(label)); } } // GOOD: Use const constructors everywhere possible const EdgeInsets.all(16); // compile-time constant const Duration(seconds: 1); // compile-time constant const TextStyle(fontSize: 14); // compile-time constant
Monitoring GC with Dart DevTools
bash# Run your Flutter app in profile mode to see real GC metrics flutter run --profile # Open DevTools and check the Memory tab: # - Monitor heap size over time # - Identify memory leaks with snapshots # - See GC events on the timeline
Summary
Dart's GC is a generational, dual-heap system optimized for Flutter's frame-budget constraints. Understanding it helps you write code that works with the GC: use const constructors, cache frequently-used objects, and avoid unnecessary allocations in hot paths like build() methods.