Garbage collection for systems programmers (2023)
Atomics, RCU, and “manual GC” patterns
- Static atomics are criticized as slow, cache-unfriendly, and not a general replacement for GC or locks.
- RCU is seen as a specialized, efficient pattern: writers make new versions, readers see a consistent snapshot, and reclamation is deferred until readers are done.
- Some argue RCU is not “really” GC because objects are in well-defined states and often reclaimed quickly; others note it still fits a broad notion of garbage collection (objects live on past obsolescence until safe to free).
GC vs RAII and external resource management
- Many note GC only solves heap memory, not file handles, sockets, mmaped files, etc.
- RAII / deterministic destruction (C++, Rust) is praised for unified handling of memory and external resources.
- Finalizers in GC languages are described as non-deterministic, tied to memory pressure, and risky for external resources; patterns like
try-with-resources/usinghelp but have edge cases (e.g., exceptions between allocation and guard). - Some GC ecosystems provide additional language features, ownership wrappers, analyzers, or structural typing to approximate RAII, with mixed satisfaction.
Tracing GC vs Reference Counting and manual management
- Tracing GCs (ZGC, Shenandoah, .NET, Go-style) are argued to beat naive manual management and simple refcounting on throughput, especially with generational and concurrent designs.
- Others counter that refcounting can be more predictable, deterministic, and have lower footprint, and is preferred in Rust/Swift-like niches.
- There’s disagreement whether refcounting has lower latency: some say cascading deletions are problematic; others argue pauses are local to a thread, unlike stop-the-world.
- Tradeoff often framed as: tracing GC = higher throughput + abstraction, worse footprint; refcounting/RAII = better footprint, more programmer effort, more API coupling.
Latency, real-time, and “GC myths”
- Pro-GC comments claim modern concurrent GCs have sub‑millisecond pauses, often dwarfed by OS pauses, and are “good enough” for most server workloads.
- Low-latency practitioners (finance, audio, networking, codecs, routing) insist that even 500 µs spikes are unacceptable where budgets are 2–10 µs end‑to‑end; these domains largely reject GC.
- Some note manual or refcount-based systems also have unpredictable tail latencies (e.g., big destructors, allocator behavior), but defenders say at least pauses are localized.
Arenas, ownership, and abstraction boundaries
- Several argue most objects fall into clear lifetime buckets (stack, per-request/frame arenas, long-lived pools), making GC overkill; others respond that “medium-lived” and shared objects, async work, and caching quickly break simple arena designs.
- Manual memory management is criticized for leaking into APIs (e.g., switching to shared ownership breaks signatures), harming modularity.
- Rust-style ownership is praised for unifying memory and I/O safety but can make some patterns (self-referential structures, certain shared topologies) awkward or impossible without indirection.
Language experiences and tooling
- Java: modern foreign-memory APIs reduce JNI pain; ZGC/GenZGC reported with very low pauses, though memory overhead and tuning remain concerns.
- .NET: GC generally liked, but deterministic cleanup (IDisposable,
using) is a persistent pain; newer features (analyzers, pooled async/ValueTask) help. - Rust: async/await without GC is described as complex (pinning, lifetimes), and some find it a “hellscape” compared to GC’d languages.
- Nim (defaulting to RC) is cited as a positive example where most code ignores memory details, improving maintainability.
Research and OS-level ideas
- New research systems (MPL/MaPLe) advertise “provably efficient” parallel GC and automatic granularity control; authors claim strong theoretical guarantees and promising benchmarks.
- Some imagine OS-level garbage collection integrated with language runtimes, but note this is largely unrealized outside research systems.