2026-10-31·作者 Sijie Wang

JVM Memory Model and GC

JVM memory model and GC: who owns memory, and how the dead are collected

The motivation. Java memory is neither managed by hand as in C, nor by compiler plus runtime in one stroke as in Go: the JVM manages allocation and reclamation for you. But managed does not mean unknowable: out-of-memory errors, GC pauses, and memory leaks are all diagnosed from this picture.

Runtime data areas, thread-private and shared. Thread-private, one copy per thread: the program counter (the current bytecode line number, the only area that never throws OutOfMemoryError), the VM stack (one stack frame per method call, holding a local variable table and an operand stack), and the native method stack (the same idea for native methods). Shared across threads: the heap (object instances and arrays, generational, below) and the method area (class metadata, constant pool; since JDK 8 called Metaspace, held in native memory, no longer inside the heap; the old PermGen is gone).

GC answers two questions: which are dead, and how are the dead collected. Which are dead uses reachability analysis: starting from GC Roots, whatever is reachable through reference chains is alive, whatever is unreachable is dead. GC Roots include locals in stack frames, static fields, constant references, and JNI references.

How the dead are collected rests on the generational hypothesis: most objects die young, and the longer an object lives, the longer it keeps living. So the heap splits into a young generation and an old generation. The young generation is Eden plus two Survivor spaces (from and to): new objects enter Eden, a Minor GC copies survivors back and forth between the Survivors, and objects old enough (default age 15, with dynamic age determination) promote to the old generation. A full old generation triggers a Major GC, using mark-sweep or mark-compact. Collection pauses the application threads: STW (Stop-The-World), and pause time comes from there.

Collectors, as a map. Serial (single-threaded, essentially unused on servers today), Parallel (throughput-first, the JDK 8 default), G1 (the JDK 9+ default: the heap cut into regions, mixed collection), ZGC and Shenandoah (pause-first: colored pointers plus load barriers, millisecond-scale pauses). The trade-off against Parallel: Parallel minimizes total GC time regardless of single-pause length, while G1 and ZGC spend extra total CPU (concurrent marking, barriers, region bookkeeping) for shorter single pauses. So on batch and offline work where pauses do not matter, Parallel wins outright; G1 can fall back to a Full GC when evacuation fails (heap too small, promotion too fast, many humongous objects), and ZGC needs heap headroom because concurrent collection must outrun allocation, and pre-generational ZGC (generational ZGC arrived in JDK 21) marked the whole heap for short-lived-heavy workloads. Default parameters also skew naive comparisons: G1's default 200ms pause target (MaxGCPauseMillis) makes it collect small and often at throughput's expense, while Parallel sets no pause target at all.

Contrast with Go GC. Go runs one algorithm everywhere: tricolor marking plus write barriers, with stop-the-world at both ends. The JVM runs generations plus a choice of collectors: the same problem, two answers.

關於這一條

sijie 的一條 wiki 條目。本站的 AI 以同一套語料庫為依據、以 sijie 的口吻作答,並附有指向此類條目的引用——回答會花 sijie 的錢,所以它在存取碼後等待: 輸入存取碼 →

JVM Memory Model and GC — Sijie Wang