Why architecture matters here
Collector choice is workload-dependent. A batch job with 30 GB heap and no pause requirement prefers G1 or Parallel. A low-latency service with 10 ms SLO and 100 GB heap wants ZGC. A microservice at 512 MB with tight memory prefers Serial or G1 depending on cores.
The architecture matters because misconfigured GC dominates the tail. Long pauses cause request timeouts, connection resets, and cascading retries. Wrong heap sizing causes constant GC or OOMKilled. Ignoring container awareness leads to JVMs using twice the memory the pod is allowed.
Knowing the internals lets you pick and tune with confidence, not folklore.
The architecture: every piece explained
The top strip is shared JVM machinery. Application threads allocate objects and read/write references. Heap regions — fixed-size chunks in modern collectors — divide the heap so collection can be incremental. TLABs (thread-local allocation buffers) let threads bump-pointer allocate without contention. Write barriers record cross-region references so old-generation collection can find roots without scanning the whole heap.
The middle row is the collectors. G1 uses region-based mixed collection: young collection is stop-the-world but bounded; mixed collection includes some old regions each cycle. ZGC uses colored pointers (metadata in address bits) and concurrent everything — marking, relocation, remap — with sub-millisecond pauses even on multi-TB heaps. Shenandoah uses Brooks forwarding pointers for concurrent relocation and per-region collection. Metadata + code cache reminds you that heap is not the only memory — Metaspace and JIT code cache also grow.
The bottom rows are practice. Tuning picks heap size (Xms=Xmx for predictability), pause target (G1's -XX:MaxGCPauseMillis), and collector flags. Observability means unified GC log (-Xlog:gc*), JFR flight recordings, and cause analysis. Ops adds container awareness (JVM sees cgroup limits), native memory tracking, and off-heap monitoring.
End-to-end flow
End-to-end: a payments service runs on Java 21 with a 32 GB heap and 20 ms pause SLO. Team picks ZGC. Application threads allocate via TLABs. ZGC concurrently marks live objects using colored pointers; the barrier fixes up stale references on access. Concurrent relocation moves live objects to fresh regions; freed regions become available. A GC cycle completes with two ~200 microsecond pauses. GC log records total pause well under SLO. Under a memory pressure test, allocation rate spikes; ZGC increases cycles per second; heap headroom stays adequate. If headroom drops below threshold, an alarm fires and the service auto-scales. All decisions logged and reviewed weekly.