Memory Model & Garbage Collection — Interview Q&A Matrix
Parent stack: Core Java Runtime Technology: Memory Model & Garbage Collection Levels: Senior Questions: 2
Each entry contains a Question, the Expected Depth, an Ideal Answer, and Red Flags.
Senior
Q1 — The Java Memory Model, volatile, and happens-before
Question: A worker thread does not see an updated running flag and continues to loop. Explain the Java Memory Model issue and fix it.
class Worker implements Runnable {
private boolean running = true;
public void stop() { running = false; }
public void run() { while (running) { /* work */ } }
}Expected Depth:
- Identifies a data race: two threads access the same field, one writes it, and there is no synchronization.
- Distinguishes visibility, ordering, and atomicity.
- Explains the happens-before relationship created by a
volatilewrite and a later read of the same field. - Knows that
volatileis suitable for an independent flag but not for compound operations or multi-field invariants. - Knows other happens-before rules, including monitor unlock/lock, thread start/join, and lock release/acquire.
- Mentions safe publication and the special visibility guarantees of correctly constructed
finalfields.
Ideal Answer:
- The program has a data race because one thread writes
runningwhile another reads it without synchronization. There is no happens-before relationship between these accesses. - The Java Memory Model therefore does not require the worker to observe the new value. The JVM may keep using a stale value or move the read outside the loop during optimization.
- Declare the flag as
volatile:
class Worker implements Runnable {
private volatile boolean running = true;
public void stop() {
running = false;
}
@Override
public void run() {
while (running) {
doWork();
}
}
}- A write to a
volatilefield happens-before every later read of that same field. The worker therefore sees the update.volatilealso limits reordering around the field access. - A
volatileread or write is atomic, but a compound operation such ascount++contains a read, calculation, and write. The full operation is not atomic. Use an atomic class:
private final AtomicLong count = new AtomicLong();
count.incrementAndGet();- Use a lock when several fields must change as one invariant:
private final ReentrantLock lock = new ReentrantLock();
private long debit;
private long credit;
void transfer(long amount) {
lock.lock();
try {
debit -= amount;
credit += amount;
} finally {
lock.unlock();
}
}- Other important happens-before rules include:
- Releasing a monitor happens-before another thread later acquires the same monitor.
- A call to
Thread.start()happens-before actions in the started thread. - Actions in a thread happen-before another thread returns from
join(). - Releasing a
Lockhappens-before another thread later acquires the same lock. - Publish shared objects safely through synchronization, a
volatilereference, static initialization, or a thread-safe collection. Correctly constructedfinalfields also receive special visibility guarantees.
Red Flags:
- Says that a primitive boolean is always safely visible because reading and writing it is atomic.
- Says only "CPU cache problem" and cannot explain the data race or happens-before rule.
- Claims that
volatilemakescount++atomic. - Adds
synchronizedeverywhere without explaining what state the lock protects. - Uses
Thread.sleep()as a visibility guarantee. - Publishes a mutable object to other threads without synchronization or a thread-safe container.
Q2 — Garbage collection and tuning (G1 vs ZGC)
Question: A service has regular latency spikes that occur during GC pauses. How would you diagnose and reduce them on Java 17 or Java 21?
Expected Depth:
- Starts with measurements instead of changing JVM flags immediately.
- Reads unified GC logs and checks pause cause, duration, frequency, allocation rate, promotion rate, and live-set size.
- Distinguishes a high allocation rate from a memory leak.
- Explains the main trade-off between G1, ZGC, and Shenandoah: throughput, pause time, heap size, and CPU cost.
- Knows that generational ZGC on Java 21 requires
-XX:+ZGenerational. - Checks application allocation patterns, heap sizing, container limits, and native memory before changing the collector.
Ideal Answer:
- First, confirm that GC is the cause of the latency spikes. Enable unified GC logging and correlate pause timestamps with application latency:
-Xlog:gc*,safepoint:file=gc.log:time,uptime,level,tags- Review the logs for:
- Pause duration and cause, such as young collection, mixed collection, evacuation failure, or full GC.
- Allocation rate and how quickly objects move to the old generation.
- The live set, which is the memory still used after a major collection.
- Heap occupancy before and after collection.
- A high allocation rate creates frequent young collections but does not always mean there is a leak. A growing live set after repeated major collections is a stronger leak signal.
- G1 is the default collector on Java 17 and 21. It uses regions and performs much of its work concurrently.
MaxGCPauseMillisis a target, not a guarantee:
-XX:+UseG1GC -Xms4g -Xmx4g -XX:MaxGCPauseMillis=200- ZGC and Shenandoah are low-pause collectors. They perform more work concurrently, which may use more CPU or reduce throughput. Pause times are usually very short, but they are not guaranteed to be below one millisecond.
- On Java 21, enable generational ZGC explicitly:
-XX:+UseZGC -XX:+ZGenerational -Xms4g -Xmx4g- Check collector support in the selected JDK distribution before choosing Shenandoah:
-XX:+UseShenandoahGC -Xms4g -Xmx4g- Then investigate the application:
- Use JFR, an allocation profiler, or a heap dump to find hot allocation paths and retained objects.
- Remove unnecessary temporary objects, boxing, and oversized buffers when measurements show they matter.
- Avoid general object pooling because it can increase the live set and make GC harder. Pool only expensive resources, such as database connections.
- Keep the heap inside the container memory limit and leave space for metaspace, thread stacks, direct buffers, and native JVM memory.
- Change one setting at a time and compare pause percentiles, throughput, CPU use, and memory use under a realistic load.
Red Flags:
- Increases
-Xmximmediately without reading GC logs or checking the container limit. - Calls
System.gc()to fix latency spikes. - Treats
MaxGCPauseMillisas a guaranteed maximum pause. - Claims that ZGC always has sub-millisecond pauses with no throughput or CPU cost.
- Enables
-XX:+UseZGCon Java 21 and incorrectly calls it generational ZGC without-XX:+ZGenerational. - Assumes every high allocation rate is a memory leak.
- Recommends pooling all objects without measuring the effect on the live set.
Scoring Rubric
| Level | Pass bar |
|---|---|
| Junior | Explains basic memory and GC concepts correctly. |
| Senior | Uses evidence to diagnose visibility and GC problems and explains the trade-offs. |
| Architect | Defines safe memory and GC standards and validates them under realistic load. |