Garbage Collector Tradeoffs

Garbage collectors trade application work, collection work, latency, and memory according to a runtime's design and the workload it runs. Tracing, reference counting, and generations describe different mechanisms; they are not mutually exclusive product categories or automatic performance rankings.

Start with the application's actual constraint: throughput, response-time distribution, memory limit, or retained objects. Measure that constraint alongside allocation and collection behavior. Reclaiming an object is not the same result as lowering process memory, returning pages to the operating system, or meeting a latency target.

TL;DR

Quick Example

This CPython-specific illustration contrasts an ordinary object with an unreachable self-cycle. Automatic cyclic collection is temporarily disabled inside this small experiment and its prior setting is restored. Run it only as an isolated teaching program, not as a suggested production tuning change.

In CPython, the non-cyclic object's weak reference cleared after its last strong reference was deleted. The cycle remained until an explicit collection, after which its weak reference cleared too. The example demonstrates reachability only; it says nothing about elapsed time, memory use, RSS, allocation rate, or returning memory to the operating system.

CPython's documentation describes cyclic collection as supplementing reference counting. The observed distinction belongs to this runtime and example; it is not a promise that every language reclaims an object at the same point. Python 3.12 gc documentation.

Core Concepts

Tracing starts from reachability

A tracing collector identifies live objects by following references from roots. Microsoft's .NET fundamentals describe roots and a reachable-object graph, along with marking and possible relocation and compaction. The collector can then reclaim objects outside the live graph. The exact phases and concurrency behavior depend on the runtime and collector. .NET GC fundamentals.

For an application investigation, ask why an object remains reachable. A cache, subscriber list, static field, queue, or pending operation can legitimately keep it live. Invoking collection does not make an intentionally retained object unreachable. Fixing an unintended ownership path can be more useful than changing collector settings.

Reference counting and cycle collection can coexist

Reference counting tracks references as ownership changes, allowing some objects to be reclaimed when their count reaches zero. A cycle can retain references internally even after the application loses its external path to the group. CPython combines reference counting with a separate cyclic collector, as the example demonstrates. Python cyclic collector interface.

Do not infer bounded application latency from eager reclamation. The amount of work associated with releasing an object and its dependencies still matters. Conversely, the fact that a runtime traces objects does not establish that every collection performs one large identical pause. Investigate the actual implementation and measured workload rather than extrapolating from the mechanism's name.

Generations target a lifetime pattern

Generational schemes organize work around object survival. The .NET documentation explains separating younger and older objects so collections can focus on a portion of the heap. That can be useful when many objects die young, but it does not make every workload fit the same lifetime distribution. .NET generations.

Measure how much data survives, how long it remains live, and what creates it. A request that creates many short-lived values differs from a service that grows a long-lived index. Reducing allocation volume can help one problem while leaving another unchanged. Keep the allocation and retention questions separate.

Pause, throughput and overhead need a common workload

Oracle's Java tuning introduction describes multiple HotSpot collectors and different application requirements, including generational, parallel, concurrent, and compaction techniques. It does not supply a universal winner for every service. Use the selected runtime's supported configurations and an actual application objective. Java 25 GC tuning introduction.

Compare candidates using the same work, arrival pattern, data sizes, memory limits, and acceptance criteria. Record warmup and steady-state behavior separately when relevant. A lower average pause can coexist with worse overall response time if other work or resource pressure changes. Report measured outcomes with their conditions rather than a collector league table.

Best Practices

Connect collection evidence to application symptoms

Capture runtime and collector version, configuration, workload identity, allocation and retention evidence, collection events, CPU, memory, and the response-time metric that matters. Align the timelines. A collection near a slow request is a useful lead, not a causal conclusion by itself.

Change one meaningful factor and repeat the representative experiment. Verify that the application still produces the same result. An optimization that drops work or retains less required data is not an equivalent comparison.

Separate memory management from resource cleanup

An unreachable managed object and a closed operating-system resource are different concerns. Microsoft's guidance calls for explicit cleanup of unmanaged resources through the appropriate ownership mechanism. Apply the language's supported resource-lifetime constructs instead of depending on collection timing to release a file or network resource. .NET unmanaged-resource guidance.

Prefer evidence over a tuning ritual

Do not add forced collections or disable collection simply because a small demonstration becomes easier to observe. The example does that to isolate one lifetime distinction. Production tuning needs a measured problem, supported runtime behavior, a rollback plan, and verification under realistic limits.

Common Mistakes

Calling every rise in RSS a leak

Treating generations as an alternative to tracing

Comparing collectors on different workloads

Using garbage collection for resource cleanup

FAQ

Does reference counting eliminate cycles?

Plain reference counting does not by itself establish that a mutually referencing group is unreachable from the application. CPython supplements it with cyclic collection. Other runtimes need their own implementation-specific explanation.

Does gc.collect prove memory returned to the operating system?

No. The example observes weak references clearing for selected objects. It measures neither process residency nor page return. Those require separate evidence.

Which collector should a service use?

Begin with a supported default and the service's measured constraints. Evaluate alternatives only against a representative workload, correct results, resource limits, and the metric that motivated the investigation.

Related Topics

References