Sitemap
RZUN

RZUN

Code crafter, full-stack builder, and AI tinkerer with 11+ years shaping secure products in banking, healthcare, and IAM.

Java Memory Management

12 min readOct 18, 2026

--

Part 1: Garbage Collection

Press enter or click to view image in full size
Photo by Markus Winkler on Unsplash

An interview-focused guide to how the JVM finds, reclaims, and manages memory — updated for Java 21 and Java 25

Garbage Collection (GC) is one of those Java topics that sounds simple at first: the JVM removes objects that are no longer needed. In interviews, however, the discussion quickly moves into reachability, GC roots, generations, pause times, collectors, promotion, compaction, and choosing the right collector for a workload.

This article is designed as a revision guide. The goal is not to memorize every JVM flag, but to build a mental model strong enough to explain what the collector is doing, why it is doing it, and what trade-offs different collectors make.

Series: Java Memory Management — Part 1: Garbage Collection. Part 2 will cover JVM tuning. Part 3 will cover memory leaks

1. The 60-second mental model

A Java application uses several memory areas, but for Garbage Collection the most important distinction is between stack-based execution state and objects stored on the heap.

Press enter or click to view image in full size

When a method returns, its stack frame is removed. Heap objects are different: an object may outlive the method that created it because some other live object, thread, static field, JNI handle, or JVM structure may still reference it. That is why heap reclamation cannot simply happen when a method ends.

Interview takeaway: Java GC is primarily about reachability, not about whether a variable went out of scope or whether a method returned.

2. When is an object eligible for Garbage Collection?

The interview-safe definition is: an object becomes eligible for garbage collection when it is no longer reachable from any GC root.

GC roots are starting points that the JVM treats as definitely alive. Common examples include references from live thread stacks, static fields of loaded classes, JNI references, active monitors, and certain JVM-internal structures.

Person p = new Person();

// The object is reachable through p
p = null;

// If no other reachable object points to that Person,
// it is now eligible for garbage collection.

Eligibility does not mean immediate removal. It only means the JVM is allowed to reclaim that object during a future GC cycle.

Calling System.gc() does not give application code deterministic control over collection. It is a request to the JVM and may be ignored or handled differently depending on configuration and collector.

Common interview trap: ‘No stack reference’ is too narrow. An object can remain alive through another heap object, a static reference, JNI, a class loader, or another GC root.

3. Reachability beats reference counting

A tempting implementation is reference counting: keep a counter for every object, increment it when a reference is added, and decrement it when a reference disappears. When the count reaches zero, reclaim the object.

The problem is cycles. Two objects can reference each other while the entire cycle is unreachable from the application.

A -> B
^ |
|____|

No GC root can reach A or B.

Their reference counts would remain non-zero, yet both objects are garbage. Modern tracing collectors instead start from GC roots, discover the reachable object graph, and consider the rest reclaimable.

4. The fundamental GC operations: mark, sweep, copy, compact

Mark

The collector identifies live objects by tracing references from GC roots. Conceptually, every object reached during this traversal is marked as live.

Sweep

After marking, memory occupied by unmarked objects can be reclaimed. A pure mark-sweep approach can leave holes between live objects, which causes fragmentation.

Compact

Compaction moves live objects together so that free memory becomes contiguous. The trade-off is additional work to relocate objects and update references.

Copy / evacuation

Instead of sweeping dead objects in place, a collector can copy or evacuate live objects into another region and then reclaim the old region in bulk. This is especially effective when most objects in the collected area are dead.

Press enter or click to view image in full size

5. Why the heap is generational

Most applications create many short-lived objects: request DTOs, temporary collections, intermediate strings, stream pipeline objects, JSON structures, and similar data. This observation is often summarized as the weak generational hypothesis: most objects die young.

Generational collectors exploit this by treating recently allocated objects differently from long-lived objects.

Young generation

New objects are normally allocated in the young generation. In traditional generational layouts this is described using Eden plus survivor spaces. G1 expresses the same idea using regions that are currently designated as Eden or Survivor regions.

A young collection reclaims dead young objects and evacuates surviving objects. Objects that survive enough collections may be promoted to the old generation. Promotion is based on collector heuristics and object age; it is not simply ‘survive once and move to old’.

Old generation

The old generation contains objects expected to live longer. Because a larger fraction of these objects may still be alive, collecting old regions is generally more expensive than collecting young regions.

Remembered sets and write barriers

If the JVM collected only the young generation, it would still need to know whether an old object points to a young object. Collectors therefore maintain remembered-set/card-table style metadata using write barriers. This avoids rescanning the entire old generation on every young collection.

A strong interview answer connects generations to object lifetime distribution: frequent cheap collection of short-lived objects, less frequent work on long-lived objects.

6. Metaspace: what it is — and an important correction

Before Java 8, HotSpot used PermGen for class metadata. Java 8 replaced PermGen with Metaspace, which uses native memory rather than the Java heap.

Metaspace stores JVM metadata associated with loaded classes. A common oversimplification is to say that class metadata can be removed once there are no instances of that class. That is not correct. Class unloading is tied to class-loader reachability: the defining class loader and its classes must become unloadable. A class can remain loaded even when zero instances currently exist.

Interview takeaway: Heap OutOfMemoryError and Metaspace OutOfMemoryError are different failure categories. Metaspace is native memory used for class metadata, and class-loader leaks are a classic cause of metaspace growth.

7. Stop-the-World vs concurrent GC

Some GC work requires application threads to reach a safe state. During a Stop-the-World (STW) pause, application threads are paused while the JVM performs required GC work.

Concurrent collectors move a large portion of expensive GC work so that it runs while application threads continue executing. ‘Concurrent’ does not mean ‘zero pauses’: even low-latency collectors still have short coordination phases.

Press enter or click to view image in full size

Parallel and concurrent are not synonyms. A collector can be parallel but Stop-the-World, concurrent, or a mixture of both.

8. The collectors you should know for interviews

Press enter or click to view image in full size

Serial GC

Serial GC does GC work with a single collector thread. Its simplicity and low overhead can make it appropriate for small heaps or constrained workloads, but its stop-the-world pauses make it a poor match for large latency-sensitive services.

java -XX:+UseSerialGC MyApplication

Parallel GC

Parallel GC is often called the throughput collector. It uses multiple threads to speed up collection, but application threads are still stopped during the main collection work. It can be a good choice when aggregate throughput matters more than tail-latency targets.

java -XX:+UseParallelGC MyApplication

G1 GC

G1 divides the heap into many equal-sized regions rather than relying only on a few large contiguous spaces. Regions can take roles such as Eden, Survivor, Old, or Humongous. G1 estimates collection efficiency and builds collection sets, prioritizing regions that can reclaim useful space while trying to meet a pause-time goal.

Get RZUN’s stories in your inbox

Join Medium for free to get updates from this writer.

G1 is the default collector on server-class HotSpot JVMs in current Java releases. It is a strong default because it aims to balance throughput, latency, and memory reclamation without requiring aggressive manual tuning.

java -XX:+UseG1GC -Xmx4g MyApplication

# Example pause-time goal (a goal, not a hard guarantee)
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 MyApplication

ZGC

ZGC is designed for very low pause times and large heaps. Most expensive work is concurrent with the application. Modern ZGC is generational, allowing it to collect young objects more efficiently while keeping pause times extremely small.

java -XX:+UseZGC MyApplication

ZGC uses load barriers and colored-pointer/metadata techniques to safely relocate objects while application threads continue to run. The implementation has evolved significantly across Java releases, so version context matters in interviews.

Shenandoah

Shenandoah is another low-pause collector that performs much of evacuation/compaction concurrently with application threads. Availability can depend on the OpenJDK distribution and platform. Java 25 makes its generational mode non-experimental, which is an important modern update for GC discussions.

What about CMS?

Concurrent Mark Sweep (CMS) appears in many older tutorials and interview notes. Treat it as historical knowledge, not as a collector you would select on a modern JDK. CMS was deprecated in JDK 9 and removed in JDK 14. G1 and newer low-latency collectors are the relevant modern alternatives.

If an interviewer asks about CMS, explain what problem it solved historically, then explicitly state that it is no longer available in modern JDKs.

9. Java 21 and Java 25: what changed for memory management?

The fundamentals of reachability, tracing, generations, and pauses remain the same, but the collector implementations continue to evolve. These changes are worth knowing because many interview questions still mix Java 8-era material with current JVM behavior.

Java 21: Generational ZGC arrives

Java 21 introduced Generational ZGC through JEP 439. It split ZGC’s logical heap into young and old generations so that ZGC could exploit the fact that young objects usually die quickly. In Java 21 it was enabled explicitly with the generational ZGC option rather than being the only ZGC mode.

# Java 21: opt into Generational ZGC
java -XX:+UseZGC -XX:+ZGenerational MyApplication

Why it matters: generational collection can reduce the amount of work required to reclaim short-lived objects, improving allocation headroom and CPU efficiency for many workloads while retaining ZGC’s low-pause design.

Java 23 and 24: ZGC completes the transition

In JDK 23, generational ZGC became the default ZGC mode and the old non-generational mode was deprecated. In JDK 24, the non-generational mode was removed. That means on Java 25, ‘ZGC’ effectively means Generational ZGC; you normally just enable -XX:+UseZGC.

Java 25: Generational Shenandoah becomes non-experimental

JDK 25 makes Shenandoah’s generational mode non-experimental. This matters for interview discussions because both of the major low-pause concurrent collectors now have a generational story in modern OpenJDK.

# Where Shenandoah is available in your JDK distribution
java -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational MyApplication

Java 25: Compact Object Headers

Java 25 also turns Compact Object Headers into a production feature. This is not a garbage collector itself, but it is directly relevant to memory management: object headers can be represented more compactly, reducing per-object overhead and potentially reducing heap footprint for object-heavy applications.

# Java 25: enable compact object headers
java -XX:+UseCompactObjectHeaders MyApplication

For interviews, the key idea is more valuable than memorizing bit layouts: less header overhead means more useful objects fit in the same heap, which can indirectly reduce allocation pressure and GC frequency in suitable workloads. In Java 25 the feature is available but is not simply something to assume is enabled in every runtime configuration.

Java 25 default: G1 remains the general-purpose default

Despite advances in ZGC and Shenandoah, G1 remains the default collector on server-class HotSpot systems in Java 25. This is a useful answer when an interviewer asks ‘Which GC does Java use by default?’: answer with the JDK/runtime context rather than naming one collector universally.

10. Minor GC, Major GC and Full GC — terminology caution

You will still hear terms such as Minor GC, Major GC, and Full GC. They are useful shorthand, but their exact meaning can vary by collector and tooling.
· Minor / Young GC usually means collection focused on the young generation.

· Major GC is often used for old-generation collection, but the term is not defined consistently across collectors.

· Full GC generally means a stop-the-world collection of the whole heap, often used as a fallback or last-resort path. The exact mechanics depend on the collector.

For modern interviews, prefer collector-specific terms such as young collection, mixed collection, concurrent mark cycle, evacuation pause, or Full GC when you know the collector involved.

11. Monitoring GC: what actually matters

A GC configuration is only ‘good’ relative to an application’s service-level goals. Monitor behavior rather than assuming a collector is automatically better.

Press enter or click to view image in full size

A healthy saw-tooth heap graph often rises with allocation and drops after GC to a roughly stable post-GC baseline. If the post-GC baseline keeps rising over time, investigate retention and potential memory leaks rather than simply increasing -Xmx.

# Basic GC logging on modern JDKs
java -Xlog:gc MyApplication

# More detailed GC information
java -Xlog:gc* MyApplication

Tools commonly used in practice include Java Flight Recorder (JFR), JDK Mission Control (JMC), VisualVM, GC logs, jcmd, heap dumps, and observability/APM platforms.

12. How to choose a collector — interview decision framework

Press enter or click to view image in full size

Do not answer ‘ZGC is the best collector’. The right collector depends on throughput targets, latency SLOs, heap size, allocation rate, available CPU, container limits, and the application’s live-set behavior.

13. Interview questions you should be able to answer

  1. What makes an object eligible for garbage collection?
  2. What are GC roots? Give examples.
  3. Why does Java use tracing instead of simple reference counting?
  4. Explain mark-sweep, mark-compact, and copying/evacuation.
  5. Why is the Java heap generational?
  6. What are Eden, Survivor, and Old generation?
  7. What is object promotion?
  8. Why are remembered sets/write barriers needed?
  9. What is Stop-the-World? Can a concurrent collector still have STW pauses?
  10. What is the difference between parallel and concurrent GC?
  11. Compare Serial, Parallel, G1, ZGC, and Shenandoah.
  12. What is the default GC in modern server-class HotSpot?
  13. Is CMS still available?
  14. What changed in ZGC from Java 21 to Java 25?
  15. What are Compact Object Headers in Java 25, and why can they matter for GC pressure?
  16. Why might increasing heap size reduce GC frequency but increase some collection costs?
  17. What would you monitor before tuning the garbage collector?

14. Rapid revision sheet

· GC reclaims heap objects that are unreachable from GC roots.

· Eligibility for GC is not the same as immediate collection.

· Tracing handles cyclic object graphs; basic reference counting does not.

· Mark identifies live objects; sweep reclaims dead memory; compact removes fragmentation; evacuation copies live objects out of a region.

· Generational GC exploits the fact that most objects die young.

· Young objects are typically allocated in Eden/young regions; survivors may age and eventually be promoted.

· Remembered sets and write barriers track cross-generation references.

· Stop-the-World pauses suspend application threads; concurrent GC overlaps work with the application but still has small pauses.

· Parallel = GC work uses multiple GC threads. Concurrent = GC work overlaps with application threads.

· G1 is the default collector on modern server-class HotSpot, including Java 25.

· CMS is historical: deprecated in JDK 9, removed in JDK 14.

· Java 21 introduced Generational ZGC; Java 23 made it the default ZGC mode; Java 24 removed non-generational ZGC.

· Java 25 makes Generational Shenandoah non-experimental where Shenandoah is supported.

· Java 25 Compact Object Headers can reduce per-object memory overhead; the feature is memory-management relevant but is not itself a GC algorithm.

· Tune based on measurements: allocation rate, live set, pauses, GC CPU, promotion, old-gen occupancy, and latency SLOs.

15. Final perspective

Garbage Collection becomes much easier to reason about when you stop thinking of it as ‘the JVM deletes unused objects’ and instead think in terms of reachable object graphs, allocation pressure, generations, relocation, and latency/throughput trade-offs.

For interviews, focus on the mental model first. If you can explain why an object is alive, why young objects are collected differently, why fragmentation matters, and why a low-latency collector trades CPU for shorter pauses, you can reason through most follow-up questions even when you do not remember a JVM flag.

Next in the series: Part 2 — JVM Tuning. We will connect heap sizing, GC logs, pause goals, allocation pressure, container memory, and collector-specific tuning into a practical troubleshooting workflow.

--

--