Glossary · G

Garbage Collection

Automatic reclamation of memory that a program no longer uses, performed by the language runtime.

By Obseria Editorial TeamUpdated

Definition

Garbage collection (GC) is automatic memory management in which the language runtime finds objects that are no longer reachable from the program and reclaims their memory. It removes whole classes of bugs such as use-after-free, but costs CPU time and can pause application threads, which shows up as higher tail latency.

What garbage collection is

In languages such as Java, Kotlin, C#, Go, JavaScript and Python, developers allocate objects but do not free them explicitly. The runtime's garbage collector periodically determines which objects are still reachable from roots, such as thread stacks, global variables and registers, and reclaims the memory of everything else. Most modern collectors are tracing collectors: they mark reachable objects, then sweep or compact the rest. CPython is the notable exception, using reference counting plus a cycle collector.

GC is relevant to observability because it consumes CPU and, depending on the collector, briefly stops application threads ("stop-the-world" pauses). Both effects appear as slower requests, especially at the 99th percentile, and as lower throughput under memory pressure.

Key concepts

  • Generational hypothesis. Most objects die young. Generational collectors keep a young generation that is collected often and cheaply, and an old generation collected less often.
  • Stop-the-world vs concurrent. Some phases pause all application threads; concurrent collectors do most of their marking while the application runs, at the cost of extra CPU and barriers.
  • Moving vs non-moving. Compacting or copying collectors move objects to reduce fragmentation; non-moving collectors leave objects in place.
  • Allocation rate. The faster a program allocates, the more often the collector must run. Reducing allocations is often the most effective GC tuning.

How major runtimes do it

JVM (Java, Kotlin, Scala)

The JVM offers several collectors. G1 has been the default for most server configurations since JDK 9; it is generational, divides the heap into regions and aims for a configurable pause-time goal (-XX:MaxGCPauseMillis, 200 ms by default). ZGC and Shenandoah are low-pause collectors that do almost all work concurrently, including compaction, keeping pauses typically in the sub-millisecond to low-millisecond range independent of heap size. ZGC became generational in JDK 21, and generational mode is its default since JDK 23. Parallel GC optimizes for throughput and accepts longer pauses; Serial GC suits small heaps.

.NET

The .NET GC is generational with generations 0, 1 and 2, plus a Large Object Heap for objects of 85,000 bytes or more and, since .NET 5, a Pinned Object Heap. Gen 0 and gen 1 collections are short and stop the world; gen 2 can run as a background GC concurrently with the application. Workstation GC targets client apps, while Server GC uses a heap and GC thread per core for throughput. Since .NET 9, Server GC adapts heap count to the workload by default (DATAS).

Go

Go uses a concurrent, tri-color mark-and-sweep collector that is non-generational and non-moving. Stop-the-world phases are short, usually well under a millisecond, but marking consumes CPU and can slow goroutines that allocate heavily through mark assists. Tuning uses two knobs: GOGC (default 100, letting the heap grow by roughly 100% of the live heap before the next cycle) and GOMEMLIMIT (a soft memory limit since Go 1.19). Go 1.26 enabled the Green Tea collector by default, which improves memory locality during marking.

What to monitor

SignalWhy it matters
GC pause duration (histogram)Direct contribution to tail latency
Share of CPU spent in GCCapacity lost to memory management
Heap used after GCThe live set; a steady rise suggests a leak
Allocation rateDrives GC frequency
Collection count by generation or typeFrequent old-generation or full GCs indicate pressure

OpenTelemetry semantic conventions define runtime metrics for these signals, for example jvm.gc.duration and jvm.memory.used_after_last_gc for the JVM, with equivalents for .NET and Go. Correlate them with request latency: if p99 spikes line up with GC pauses, the collector is part of the explanation.

Best practices

  1. Measure before tuning. Collect GC metrics and pause logs in production; defaults are good for most workloads.
  2. Reduce allocations on hot paths, guided by allocation profiles and flame graphs.
  3. Size memory to the container. Make sure the runtime knows its limit: modern JVMs and .NET respect cgroup limits; for Go, set GOMEMLIMIT somewhat below the container limit.
  4. Pick the collector for the goal. Low-latency services benefit from ZGC or Shenandoah on the JVM; batch jobs often prefer throughput-oriented collectors.

Common mistakes

Teams often copy GC flags from blog posts without measuring, raise the heap until the problem disappears temporarily, or confuse a memory leak with GC pressure. A leak shows as a heap-after-GC line that never returns to its baseline; GC pressure shows as frequent collections with a stable live set.

How Obseria helps

Obseria collects runtime GC metrics through OpenTelemetry and the Obseria Agent for the JVM, .NET and Go, and places them next to request latency. Continuous profiling adds allocation profiles, so you can see which code creates the garbage.

Frequently asked questions

Most collectors have at least short stop-the-world phases, but their length varies widely. Concurrent collectors such as ZGC, Shenandoah or Go's GC keep pauses very short, typically around a millisecond or less, by doing most work while the application runs.

Compare GC pause metrics or GC logs with request latency over the same period. If p99 latency spikes coincide with long pauses or a high share of CPU in GC, the collector is a likely contributor.

No. Go's collector is concurrent, non-generational and non-moving. It is tuned with GOGC and, since Go 1.19, GOMEMLIMIT.

With a leak, the heap remaining after each collection keeps growing because objects stay reachable. With GC pressure, the live set is stable but a high allocation rate forces frequent collections.

Put these concepts into practice

Try Obseria free for 14 days, with OpenTelemetry and no proprietary agents.