Lesson 24 min

Garbage Collection Internals

00:00 / 00:00

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.

WhatsApp