Baffled by generational garbage collection – wingolog

When generational GC helps (and when it doesn’t)

  • Several comments stress that generational GC only shines when workloads match the “most objects die young, survivors live long” pattern and there is a large mature heap.
  • Benchmarks like splay may churn the entire heap, so minor collections give little benefit and can even add extra copying cost.
  • Big real-world systems (servers, GUIs, dynamic languages) often do fit the generational profile: many short-lived temporaries per request/event, plus a large, mostly-stable object graph.

Java and JVM techniques

  • JVM is said to handle short-lived objects very well via Thread-Local Allocation Buffers (bump-pointer allocation) and escape analysis, sometimes effectively turning heap allocations into stack-like behavior.
  • Generational, pauseless collectors (G1, Shenandoah, ZGC) are described as state-of-the-art; ZGC in particular is cited for sub-millisecond pauses.
  • Object pools in Java were historically used to reduce GC pressure but are criticized:
    • Can harm generational GC by creating many old-to-young references, forcing more frequent/expensive full GCs.
    • Are error-prone (double-returns, use-after-free of pooled objects).
  • Binary-trees benchmark is used as an example where Java’s generational + TLAB approach competes with or beats arena-based native code.

Go’s non-generational, concurrent GC

  • Go’s GC is concurrent, non-generational, and explicitly optimized for latency, with “GC assist” that throttles heavy allocators.
  • Supporters like that it rarely breaks SLOs and usually “just works” without tuning.
  • Critics say throughput is worse than modern JVM/.NET GCs and there are scaling issues at large heaps or high core counts, with few tuning “escape hatches.”
  • Go relies heavily on stack allocation via escape analysis, so it simply creates less garbage.

.NET and C# perspectives

  • .NET has long used generational GC with server/workstation modes and newer options to behave more or less “cooperatively” with other processes.
  • It’s portrayed as close to Java in GC sophistication, often more memory‑efficient, and helped by value types and lower allocation rates.
  • Discussion diverges into whether highly optimized C# can rival C/Rust performance; opinions differ, but many agree C# has gained powerful low-level features (spans, ref structs, custom allocators).

Object pools, arenas, and the memory-management continuum

  • Commenters emphasize memory management as a continuum: GC, malloc variants, arenas, static layouts, custom allocators can all be mixed.
  • Java now has arena allocators; performance‑sensitive code in multiple ecosystems sometimes bypasses GC-managed heaps entirely.

Benchmarks, bias, and the generational hypothesis

  • Several note a methodological risk: if you design benchmarks and software assuming cheap young-object allocation, you’ll naturally validate the generational hypothesis.
  • Some suggest richer instrumentation (e.g., per-callsite lifetime profiling, pretenuring, multiple generations sized to cache/request lifetimes) to better test and tune generational collectors.

Debate over Whippet and Immix

  • One thread criticizes the blog author’s GC choices (mark-sweep, Immix) as “not proper” compared to classic semispace+generational designs; others push back, calling Immix state-of-the-art and noting the author has written extensively about advanced GC techniques.
  • There’s some confusion around what was said in a talk vs what exists in the code and prior posts, and no clear consensus on the quality of the author’s GC implementations.