Dart Garbage Collection


Garbage Collection in Dart

Garbage Collection (GC) in Dart is the process by which the Dart runtime automatically frees up memory that is no longer in use, which helps to prevent memory leaks. The garbage collector identifies and removes objects from memory that are no longer referenced by the application, making memory available for new objects.

Dart uses a generational garbage collection approach, which optimizes for:

  1. Short-lived objects: Objects that are created and quickly become unreachable.
  2. Long-lived objects: Objects that persist for longer, often needing fewer GC cycles.

This approach is efficient for applications like Flutter, where many temporary objects are created and quickly disposed of.

How Garbage Collection Works in Dart

  1. Object Allocation: When an object is created, memory is allocated to it on the heap.
  2. Reference Tracking: The garbage collector keeps track of references to objects. When there are no references to an object, it becomes eligible for garbage collection.
  3. Garbage Collection Process: The garbage collector periodically runs to clear memory used by unreachable objects.
  4. Automatic Disposal: In most cases, garbage collection happens automatically, and developers don’t need to manually free memory.

Example Demonstrating Garbage Collection

In Dart, we don't directly see garbage collection happening, but we can observe the behavior of object references and memory cleanup by watching when objects become unreachable.

In this example, we’ll create a class Person and instantiate objects that will later be removed from memory by Dart's garbage collector once they become unreachable.

Code Example:

class Person { String name; Person(this.name) { print("$name created."); } // Destructor-like behavior (just to show when an object is no longer referenced) void dispose() { print("$name disposed."); } } void createPerson() { var person1 = Person("Alice"); var person2 = Person("Bob"); // Objects are referenced within this function. person1.dispose(); person2.dispose(); } void main() { print("Start of main"); // Create persons in a limited scope createPerson(); print("End of main - Garbage collection may run now."); }

Explanation:

  1. Creating Objects:

    • When createPerson is called, it creates two Person objects: person1 and person2.
    • Each object prints a message when it’s created, showing that memory is allocated for them.
  2. Disposal Simulation:

    • Although Dart handles garbage collection automatically, we manually call dispose here to simulate when each object would become unreachable.
    • When createPerson exits, person1 and person2 go out of scope and become eligible for garbage collection.
  3. End of Scope:

    • Once createPerson finishes, the person1 and person2 variables are no longer accessible, making them unreachable and available for garbage collection.
    • At this point, Dart’s garbage collector may clean up the memory for person1 and person2.

Output:

Start of main Alice created. Bob created. Alice disposed. Bob disposed. End of main - Garbage collection may run now.

Note: In this example, the actual garbage collection process is not displayed because Dart handles it automatically, and there are no explicit "destructor" calls in Dart. The dispose method is manually called here just to indicate where objects become unreachable.


Important Points on Garbage Collection in Dart

  • Automatic Memory Management: Dart’s garbage collector automatically frees memory when objects go out of scope and become unreachable.
  • No Explicit Destructor: Unlike languages with destructors (like C++), Dart doesn’t require or allow manual memory deallocation. This means there’s no need for destructors to clean up memory.
  • Finalizer API: Dart introduced a Finalizer API for cases where you need to run some cleanup code right before an object is collected. This is useful for native resources or open connections.

Finalizer API Example

Here's a quick example of how to use Finalizer to clean up native resources:

import 'dart:ffi'; // For native resource example class Resource { final String name; Resource(this.name); void release() { print("Releasing resource for $name"); } } void main() { final finalizer = Finalizer<Resource>((resource) { resource.release(); }); var resource = Resource("Resource1"); finalizer.attach(resource, resource); print("Resource created and ready for use"); }

In this example, Finalizer ensures that resource.release() is called right before resource is garbage collected.


Summary

Dart’s garbage collection automatically manages memory by freeing unused objects. The generational garbage collector in Dart efficiently manages both short-lived and long-lived objects, which is crucial for apps with frequent object creation, such as Flutter apps. While Dart lacks destructors, developers can use the dispose pattern or Finalizer API to handle custom cleanup tasks when necessary.