{"post":{"slug":"garbage-collection-explained-2026","title":"Garbage collection explained in 2026: how runtimes reclaim memory","excerpt":"GC pauses that once lasted hundreds of milliseconds now target sub-millisecond goals in modern runtimes. Here is how the algorithms work and when they matter.","category":"code","date":"April 13, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">G</span>arbage collection is the runtime mechanism that reclaims heap memory your program no longer references. Without it, every allocation would require a matching manual free — and in practice, humans forget. Languages from Java to Go to Python to JavaScript all ship with GC, but they differ significantly in how they find unreachable objects, when they run, and what the latency cost is.\n\n## What changed in 2026\n\n- **Sub-millisecond GC pauses are mainstream.** Go's GC targets < 0.5 ms. Java's ZGC and Shenandoah target < 1 ms on terabyte heaps. .NET's Server GC with regions achieves similar results.\n- **JavaScript engines (V8, SpiderMonkey) ship incremental+concurrent collectors.** Orinoco (V8's GC) has been fully concurrent since 2021; in 2026 it handles WebAssembly heap objects too.\n- **Rust proved the \"no GC\" alternative is viable at scale.** Rust's ownership model eliminates GC entirely by making lifetime analysis a compile-time check — at the cost of a steeper learning curve.\n- **GC-aware hardware (Intel MPX successor, CHERI) is in research but not yet production.**\n\n## How tracing GC works\n\nThe two fundamental techniques are **mark-and-sweep** and **copying/compacting**. Both start with the same root scan:\n\n```\nRoots: global variables, stack frames, CPU registers\n  ↓ Follow every reference\nMark: paint every reachable object \"live\"\n  ↓\nSweep/Compact: reclaim or move all unmarked objects\n```\n\n**Mark-and-sweep** leaves objects in place (no compaction). Fast, but causes heap fragmentation over time.\n\n**Copying GC** moves live objects to a fresh \"to-space,\" leaving the old \"from-space\" entirely free. Compacts automatically, but needs 2× the heap headroom.\n\n**Compacting GC** (like .NET) moves live objects in-place to eliminate fragmentation without doubling memory.\n\n## Generational hypothesis\n\nEmpirically, the vast majority of allocated objects become unreachable within milliseconds of allocation — function return values, loop temporaries, and so on. Generational collectors exploit this:\n\n```\nYoung Gen (nursery)   → collected frequently, cheaply (~1–5 ms)\nOld Gen (tenured)     → collected infrequently (~10–500 ms, or concurrent)\n```\n\nObjects that survive a few young-gen collections get \"promoted\" to the old gen. Full (\"major\") GC scans the whole heap and is typically triggered by old-gen pressure.\n\n| Runtime | Young gen | Old gen / Tenured |\n| --- | --- | --- |\n| JVM (G1 / ZGC) | Eden + Survivor spaces | Regions or tenured heap |\n| V8 | Scavenger (copying) | Mark-compact (concurrent) |\n| .NET | Gen0 / Gen1 | Gen2 (LOH for large objects) |\n| Python CPython | Refcount + generational cycle detector | — |\n\n## Reference counting (Python, Swift)\n\nReference counting increments a counter on every assignment and decrements on every drop. When the counter reaches zero, the object is freed immediately.\n\n```python\nimport sys\nx = [1, 2, 3]\nprint(sys.getrefcount(x))  # 2 (x + getrefcount arg)\ny = x\nprint(sys.getrefcount(x))  # 3\ndel y\nprint(sys.getrefcount(x))  # 2\n```\n\n**Advantage:** deterministic, immediate deallocation; no stop-the-world pauses.\n\n**Problem:** reference cycles (A → B → A) never reach zero. CPython adds a generational cycle detector that runs periodically to handle this. Swift uses Automatic Reference Counting (ARC) at compile time — no runtime overhead for cycle detection, but you must break cycles manually with `weak` or `unowned` references.\n\n## Profiling GC in Go\n\n```go\nimport \"runtime\"\n\nvar stats runtime.MemStats\nruntime.ReadMemStats(&stats)\nfmt.Printf(\"NumGC: %d, PauseTotal: %v ms\\n\",\n    stats.NumGC, time.Duration(stats.PauseTotalNs))\n```\n\nUse `GODEBUG=gctrace=1` to log every GC cycle to stderr:\n\n```\ngc 14 @3.141s 0%: 0.021+1.2+0.018 ms clock, ...\n```\n\nFor Java ZGC:\n\n```bash\njava -XX:+UseZGC -Xlog:gc* -jar app.jar\n```\n\n## How to pick your GC strategy\n\n| Runtime | Best GC choice in 2026 |\n| --- | --- |\n| JVM | ZGC (< 1 ms pause) or G1 (balanced) |\n| Go | Default GC is good; tune `GOGC` and `GOMEMLIMIT` |\n| .NET | Server GC with regions (default in .NET 7+) |\n| Node.js / V8 | Default concurrent GC; tune `--max-old-space-size` |\n| Python | Consider PyPy for GC-sensitive workloads |\n\n1. **Profile before tuning.** Most GC pauses are not your bottleneck.\n2. **Set `GOMEMLIMIT` in Go** (introduced 1.19) to give the GC a soft memory cap — it will collect more aggressively rather than OOM.\n3. **Avoid large object allocation in hot loops** — large objects often skip the young gen and land directly in the old gen or LOH.\n4. **Reduce allocation rate** — fewer allocations means fewer GC cycles; prefer value types and pool reuse where profiling shows hotspots.\n\n## Common mistakes\n\n**Ignoring GC metrics in production.** GC pause time and allocation rate are first-class SLIs for latency-sensitive services. Add them to your dashboard.\n\n**Object pooling everywhere.** `sync.Pool` (Go), object pools (Java), and ArrayPool (.NET) help in specific hot paths. Applied broadly they add complexity and hurt cache locality.\n\n**Holding references to large objects longer than needed.** A reference in a long-lived map keeps the entire object alive. Null / delete the reference as soon as you are done.\n\n**Setting heap too small.** A tiny heap forces constant GC. Rule of thumb: size the heap to 2–3× your live set for comfortable throughput.\n\n**Confusing GC pause with GC overhead.** Pause = stop-the-world latency per cycle. Overhead = total CPU spent on GC. Both matter; they require different fixes.\n\n## What to skip\n\n- **Manually calling `System.gc()` (Java) or `runtime.GC()` (Go)** in production — it rarely helps and often hurts by triggering a full collection at the wrong time.\n- **Assuming GC-free means Rust for everything** — Rust's ownership model has a learning curve and compile time cost; measure first.\n- **Disabling the cycle detector in Python** (`gc.disable()`) without understanding your allocation patterns — you will leak memory on any circular data structure.\n\n## FAQ\n\n**Does GC cause latency spikes in production?**\nWith modern concurrent collectors (ZGC, Go GC, V8 Orinoco) in 2026, pauses are typically < 1 ms. If you are seeing > 10 ms pauses, you likely have a heap sizing or allocation rate problem.\n\n**What is the difference between GC and a destructor?**\nA destructor (C++) runs immediately when an object goes out of scope. A GC finalizer runs at some unspecified future time when the collector finds the object unreachable — you cannot rely on it for prompt resource release.\n\n**Does Rust really have no GC?**\nCorrect. Rust's borrow checker enforces ownership and lifetime rules at compile time, so the compiler inserts dealloc calls deterministically. No runtime collector needed, but you must follow the ownership rules.\n\n**How do I reduce GC pressure in Node.js?**\nReduce heap allocation rate: reuse buffers (`Buffer.allocUnsafe` + manual reset), avoid creating closures in hot loops, and use streaming APIs rather than loading full payloads into memory.\n\n## Where to go next\n\n- [Memory management explained in 2026](/blog/memory-management-explained-2026)\n- [Concurrency explained in 2026](/blog/concurrency-explained-2026)\n- [Threads vs processes explained in 2026](/blog/threads-vs-processes-explained-2026)","hue":312,"metaTitle":"Garbage Collection Explained in 2026","metaDescription":"Plain-language guide to garbage collection in 2026. Tracing vs refcounting, generational GC, modern low-pause collectors, and how to profile GC overhead.","keywords":["garbage collection explained 2026","how garbage collection works","generational gc 2026","go garbage collector 2026","java zgc 2026","tracing vs reference counting","gc pause times 2026","memory management gc","v8 garbage collection","gc tuning 2026"],"tldr":["**Garbage collection automatically frees unreachable heap objects** — the runtime traces live references and reclaims everything else.","**Tracing GC (used by JVM, Go, V8, .NET) vs reference counting (Swift, Python CPython)** — tracing handles cycles; refcounting is more predictable but leaks on cycles without a cycle detector.","**Generational GC exploits the observation that most objects die young** — short-lived objects are collected cheaply in the \"young generation\" without scanning the whole heap.","**Go 1.21+, Java ZGC, and .NET 7+ all target < 1 ms pause times** — for latency-sensitive services, runtime GC choice matters as much as algorithm.","**Skip:** pre-allocating huge object pools \"to beat the GC\" without profiling first — object pooling adds complexity and often hurts locality."],"readTime":"5 min read","wordCount":1122},"related":[{"slug":"what-is-garbage-collection-2026","title":"What is garbage collection in 2026? Automatic memory cleanup explained","excerpt":"Garbage collection automatically frees memory your program no longer uses, so you do not have to manage it by hand. This guide explains how it works, its trade-offs, and when it matters.","category":"code","date":"June 23, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":110},{"slug":"memory-management-explained-2026","title":"Memory management explained in 2026: stack, heap, and everything between","excerpt":"Manual malloc/free, garbage collection, and Rust-style ownership are three fundamentally different tradeoffs. Here is what every developer needs to know in 2026.","category":"code","date":"March 2, 2026","author":"ByteLedger Team","readTime":"6 min read","hue":313},{"slug":"generational-garbage-collection-explained-2026","title":"Generational Garbage Collection Explained for 2026","excerpt":"Most objects die young, so most collectors split the heap into a small, frequently swept young generation and a rarely swept old one. Here is why that split works, how write barriers make it safe, and why Go skips it.","category":"code","date":"August 2, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":304}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"express-vs-fastify-2026","title":"Express vs Fastify in 2026: which Node.js framework should you use?","excerpt":"Express is the ubiquitous Node.js default; Fastify is the modern high-performance contender. Here is how they compare for real projects in 2026.","category":"code","date":"April 13, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":312},"older":{"slug":"git-rebase-vs-merge-2026","title":"Git rebase vs merge in 2026: which strategy keeps your history clean","excerpt":"The rebase vs merge debate has a clear answer once you understand what each command actually does to your commit graph and when each one creates problems.","category":"code","date":"April 13, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":313}}