Java Memory Model: interview questions & scenarios
These are the Java Memory Model questions I ask most, roughly in the order I ask them. They start with "why does this loop never stop?" and end somewhere around "what does the JIT on an ARM server do with your code?" Every answer is correct for Java 21, and every output block comes from a real run on an Apple M4 Pro (ARM, 14 cores) with JDK 21.0.12.
Here's how to use this page: read the question, answer it out loud first, then open the model answer. Memory model bugs are rare and timing-dependent, so your outputs may differ from mine; that's part of the lesson. If an answer feels thin, the lessons go deeper: Visibility and reordering, Happens-before, volatile, Safe publication and double-checked locking and Final field semantics.
This stop-flag loop never stops in production, but it works fine in the debugger. Why?
Tests visibility, JIT hoisting, volatile, interpreter vs C2
Show answerHide answer
Model answer
Here's the code, trimmed down. A worker spins until main flips a plain boolean:
public class StopFlag {
static boolean running = true; // try it with and without volatile
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
long spins = 0;
while (running) {
spins++; // no method calls in the loop body
}
System.out.println("worker stopped after " + spins + " spins");
});
worker.setDaemon(true);
worker.start();
Thread.sleep(500);
running = false;
System.out.println("main: set running = false");
worker.join(2000); // watchdog: never wait forever
System.out.println(worker.isAlive() ? "worker STILL running after 2 s" : "done");
}
}
Three real runs, then one with volatile boolean running, then one with the JIT switched off (-Xint, interpreter only):
$ java StopFlag.java (3 runs, same result every time)
main: set running = false
worker STILL running after 2 s
$ java StopFlagVolatile.java
main: set running = false
worker stopped after 1541774902 spins
done
$ java -Xint StopFlag.java
main: set running = false
worker stopped after 166109416 spins
done
That last run is your "it works in the debugger" moment. The bug isn't in the CPU. It's in the JIT compiler. After a few thousand iterations, HotSpot's C2 compiler optimises the hot loop. Nothing in the loop writes running, and nothing in it synchronizes, so the compiler is allowed to read running once and turn the loop into if (running) while (true) spins++;. The worker never looks at memory again.
A debugger, a breakpoint or -Xint keeps the code in the slow interpreter, which re-reads the field every time. So the bug vanishes the moment you go looking for it.
The fix is volatile boolean running (or an AtomicBoolean, or interrupting the thread). A volatile read can't be hoisted or cached, and the write in main happens-before every later read that sees it.
How I’d say it in the interview
"The flag isn't volatile and the loop has no synchronization, so the JIT is allowed to read it once and hoist it out of the loop. The worker then spins forever on its own copy. It works in the debugger because the interpreter re-reads the field each time. Marking the flag volatile fixes it, because a volatile write happens-before the later read that sees it, and the compiler can't cache volatile reads."
Signals depth
Blaming the compiler (hoisting the read out of the loop), not "CPU caches". Modern CPU caches are coherent; a store does become visible to other cores quickly. Bonus: knowing that adding a println or Thread.sleep inside the loop often "fixes" it by accident. The call stops the JIT from proving nothing changes running, but the JMM still gives you no guarantee, so the bug can come back on the next JDK.
Red flags
"Each core has its own copy of RAM, and volatile forces it to main memory." That's a mental model at best and wrong as hardware. Also: "I added Thread.sleep(1) in the loop and it works now, so it's fixed." JLS §17.3 says outright that sleep and yield have no synchronization semantics.
Follow-ups interviewers ask next
What if you add System.out.println inside the loop?
On HotSpot it usually starts stopping, because
printlnis a method call that takes a lock insidePrintStream, and the JIT gives up on keepingrunningin a register. But that lock isn't the same lockmainuses (main doesn't use one at all), so there's no happens-before edge. It's working by luck and side effects. Fix it properly withvolatile.What if the flag is an AtomicBoolean instead?
That's correct too.
AtomicBoolean.get()andset()have volatile semantics. Use it when you also needcompareAndSet, like "only the first caller gets to shut down". For a plain stop flag,volatile booleanis enough.
What if the worker is blocked in queue.take() instead of spinning?
Then no flag will ever reach it, volatile or not, because it isn't looking at the flag. Use
worker.interrupt(). Blocking calls liketake(),sleep()andwait()wake up withInterruptedException. That's covered in Interrupts and cancellation.
What is the Java Memory Model, and why does Java even need one?
Tests JMM definition, JLS 17.4, compiler and CPU reordering
Show answerHide answer
Model answer
The Java Memory Model (JMM) is the contract that answers one question: when one thread writes a variable, which values is another thread allowed to see when it reads it? It lives in JLS chapter 17.4.
Think of a busy restaurant kitchen. The head chef writes "table 7: no nuts" on the board. Without a rule like "always check the board before you plate", a cook might work from what they remember instead. The JMM is that rule, written down so precisely that compilers, CPUs and JVM authors can all agree on it.
Why does Java need it? Because three layers rewrite your code for speed, and none of them think about other threads by default:
| Layer | What it does to your code |
|---|---|
| javac / JIT compiler | Reorders independent statements, keeps values in registers, hoists reads out of loops |
| CPU | Executes out of order, parks writes in a store buffer before they reach the cache |
| Memory system | On ARM and POWER, lets other cores observe writes in a different order than they were issued |
Every one of those is invisible inside a single thread. That's the deal: as-if-serial semantics, your thread always sees its own code run in order. Another thread watching from outside gets no such promise unless you use the tools the JMM defines: synchronized, volatile, final, Thread.start()/join() and the java.util.concurrent classes.
The JMM is specified with happens-before, not with caches or barriers. That's on purpose. Java runs on x86, ARM, POWER and RISC-V, and each has different hardware rules. The JMM gives one portable rule; each JVM maps it to its own hardware.
How I’d say it in the interview
"The JMM is the part of the language spec that says which writes a thread is allowed to see. Compilers and CPUs reorder and cache things for speed, and that's invisible inside one thread but visible across threads. The JMM defines happens-before, and if two actions are linked by happens-before through things like locks, volatile or thread start and join, the second is guaranteed to see the first. Without that link, there's no guarantee at all."
Signals depth
Naming both the compiler and the CPU as sources of reordering, and saying the JMM is defined in terms of happens-before, with barriers being an implementation detail. Knowing the JMM was rewritten in Java 5 by JSR-133, which is when volatile and final got their current, stronger meaning.
Red flags
"The JMM is how the JVM lays out memory: heap, stack, metaspace." That's the runtime data areas, a different topic (see Stack vs heap). Mixing them up is the most common miss on this question.
Follow-ups interviewers ask next
What if my program has no data races? Do I need to think about reordering at all?
No, and that's the best part of the JMM. It promises DRF-SC: if a program is correctly synchronized (no data races), every execution looks sequentially consistent, as if all threads' actions ran one at a time in some interleaving. You only have to reason about reordering when you write racy code on purpose.
What if the code does have a data race? Can a read return any random garbage?
No. Java is stricter than C++ here. A racy read must return a value that some write actually wrote (or the default value), and the JMM's causality rules forbid "out-of-thin-air" values. Racy Java code is wrong, but it can't corrupt the JVM or invent values from nowhere. The one exception is non-volatile
longanddouble, which may be torn in half (more on that below).
What does volatile guarantee, and what doesn't it?
Tests visibility, ordering, atomicity, volatile count++
Model answer, how to say it, 2 follow-ups: unlock with the Java Pass
Visibility, reordering and atomicity: what's the difference between them?
Tests three concurrency problems, which tool fixes which
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
What is happens-before? Name the rules you actually use.
Tests happens-before, program order, monitor lock, volatile, start/join, transitivity
Model answer, how to say it, 2 follow-ups: unlock with the Java Pass
If A happens-before B, does A run before B in time?
Tests happens-before vs time, reordering, synchronization order
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
What can this print? (message passing)
Tests litmus test, reordering, volatile publication
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
Can both threads read zero here? (store buffering)
Tests store buffering, store-load reordering, volatile vs acquire/release
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
The writer sets a plain field, then a volatile one. Does the reader see the plain field?
Tests piggybacking, volatile ordering, transitivity
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
What's wrong with this double-checked locking singleton?
Tests DCL, unsafe publication, volatile, partially constructed object
Model answer, how to say it, 2 follow-ups: unlock with the Java Pass
What does "safe publication" mean? Name the ways to do it.
Tests safe publication, JCIP idioms, partially constructed objects
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
What do final fields guarantee that normal fields don't?
Tests final field semantics, JLS 17.5, immutable objects
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
The constructor registers this as a listener. Why can another thread see a 0 in a final field?
Tests this-escape, final field guarantee, constructor publication
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
Can a thread see half of a long?
Tests word tearing, JLS 17.7, long/double atomicity
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
Do you need volatile on the fields of a task you submit to an ExecutorService?
Tests executor happens-before, Future.get, memory consistency properties
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
A static HashMap is filled in a static block and then only read by many threads. Is that thread-safe?
Tests class initialization, static initializer publication, effectively immutable
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
Design a hot-reloadable config that hundreds of request threads read on every request.
Tests volatile reference to immutable object, copy-on-write, AtomicReference
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
String.hashCode() caches its result in a non-volatile field. Isn't that a data race?
Tests benign data race, idempotent computation, racy single-check
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
A test for a memory model bug passes 10,000 times in a row. Are we safe?
Tests testing concurrency, jcstress, x86 vs ARM, JIT dependence
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
How does HotSpot actually implement volatile on x86 and on ARM?
Tests memory barriers, TSO, ldar/stlr, cost of volatile
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
What are VarHandle access modes, and when would you use acquire/release instead of volatile?
Tests VarHandle, plain/opaque/acquire-release/volatile, memory order modes
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
Is it OK to read a field guarded by a lock without taking the lock, "just for a quick check"?
Tests lock-based happens-before, both sides must synchronize, racy reads
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
Explain the JMM to a junior engineer in two minutes, without saying "cache".
Tests communication, mental model, practical rules
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
Premium question set
Keep reading with the Java Pass
The rest of this question set is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
From ₹299 · one-time payment · No auto-renewal