Course contents

Java Memory Model: interview questions & scenarios

Premium23 questions · 29 what-if follow-ups

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.

Question 1. ScenarioWarm-up

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 answer3 follow-ups

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

  1. What if you add System.out.println inside the loop?

    On HotSpot it usually starts stopping, because println is a method call that takes a lock inside PrintStream, and the JIT gives up on keeping running in a register. But that lock isn't the same lock main uses (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 with volatile.

    1. What if the flag is an AtomicBoolean instead?

      That's correct too. AtomicBoolean.get() and set() have volatile semantics. Use it when you also need compareAndSet, like "only the first caller gets to shut down". For a plain stop flag, volatile boolean is enough.

  2. 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 like take(), sleep() and wait() wake up with InterruptedException. That's covered in Interrupts and cancellation.

Question 2. ConceptWarm-up

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 answer2 follow-ups

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

  1. 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.

    1. 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 long and double, which may be torn in half (more on that below).

Question 3. ConceptWarm-up

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

Question 4. ConceptCore

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

Question 5. ConceptCore

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

Question 6. ConceptCore

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

Question 7. What does it print?Core

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

Question 8. What does it print?Senior

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

Question 9. What does it print?Core

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

Question 10. DebuggingCore

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

Question 11. ConceptCore

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

Question 12. ConceptCore

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

Question 13. DebuggingSenior

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

Question 14. ConceptCore

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

Question 15. ScenarioCore

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

Question 16. ScenarioCore

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

Question 17. DesignSenior

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

Question 18. ConceptSenior

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

Question 19. DebuggingSenior

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

Question 20. ConceptSenior

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

Question 21. ConceptSenior

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

Question 22. ScenarioCore

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

Question 23. DesignSenior

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

Lessons in Java Memory Model