Understanding JVM Memory Structure and the Java Memory Model (JMM): volatile, synchronized and Concurrency Basics
This article explains the differences between JVM memory layout and the Java Memory Model, clarifies how volatile and synchronized guarantee visibility, atomicity and ordering, and provides practical guidelines, code examples, and diagrams for mastering Java concurrency fundamentals.
JVM Memory and JMM Memory Model
In interviews and concurrent programming you often encounter volatile and synchronized. This article summarises the core knowledge points and interview focus, using text and diagrams to improve your concurrency skills.
Key Questions
What is the difference between JMM and JVM memory structure?
What is the Java Memory Model (JMM) and how does it relate to concurrency?
What are the most important aspects of the memory model: instruction reordering, atomicity, memory visibility?
What does volatile visibility mean, its use cases and common pitfalls?
How does synchronized work internally and what is its relationship with the monitor?
JVM Memory Structure
Java source files are compiled to .class files, loaded by the JVM class loader, and executed either by the interpreter or the JIT compiler on the underlying OS. The JVM abstracts the hardware, enabling Java to run on Linux, Windows, macOS, etc.
Since Java 8 the permanent generation has been removed and replaced by Metaspace, so flags like -XX:PermSize and -XX:MaxPermSize are obsolete.
The JVM divides memory into several runtime data areas:
Heap : stores objects and arrays; the main area for garbage collection.
Java Virtual Machine Stack : per‑thread stack of stack frames that hold local variables, operand stacks, dynamic linking information and return addresses.
Method Area (Metaspace) : stores class metadata, constant pools, field and method data.
Native Method Stack : used for native (C/C++) method execution.
Program Counter (PC) Register : points to the next bytecode instruction for each thread.
<code style="padding:16px;color:#abb2bf;display:-webkit-box;font-family:Operator Mono,Consolas,Monaco,Menlo,monospace;font-size:12px"><span style="color:#c678dd">public</span> int add() {
int a = 1, b = 2;
return a + b;
}
</code>Each stack frame contains a local variable table, an operand stack, dynamic linking information and a return address.
Java Memory Model (JMM)
The JMM is a set of specifications that all JVM implementations must follow to guarantee consistent behavior of multithreaded programs across different hardware and compiler optimizations.
It addresses problems caused by CPU caches, instruction reordering, and compiler optimizations, ensuring that keywords such as volatile, synchronized and classes in java.util.concurrent work correctly.
The three most important concepts of JMM are:
Instruction Reordering
Atomicity
Memory Visibility
Instruction Reordering
Compilers, the JVM and CPUs may reorder instructions for performance, as long as single‑threaded semantics are preserved. In multithreaded code this can lead to unexpected results.
Reordering can reduce the number of loads and stores, improving speed, but it must not change the program order visible to a single thread.
Memory Visibility
When a thread writes to a volatile variable, the new value is immediately flushed to main memory, making it visible to other threads.
<code style="padding:16px;color:#abb2bf;display:-webkit-box;font-family:Operator Mono,Consolas,Monaco,Menlo,monospace;font-size:12px"><span style="color:#c678dd">public</span> class Visibility {
int x = 0;
public void write() { x = 1; }
public void read() { int y = x; }
}
</code>If a thread updates x in its working memory but does not synchronize with main memory, another thread may still see the old value, causing a visibility problem.
Atomicity
Read/write of primitive types (except non‑volatile long and double) are atomic. Compound actions like i++ are not atomic and require additional synchronization.
How JMM Solves These Problems
JMM defines two abstract memories:
Main Memory : shared by all threads.
Working Memory : a thread‑local copy of variables.
Eight atomic actions control the interaction between them:
read load store write use assign lock unlockThese actions, together with memory barriers, guarantee the three JMM guarantees (reordering, atomicity, visibility).
volatile
volatileensures that writes to a variable are immediately visible to other threads and prevents certain kinds of reordering.
Visibility : a write to a volatile variable happens‑before any subsequent read of that variable.
Prohibits Reordering : the compiler and CPU cannot reorder operations that cross a volatile read/write.
Correct Usage
Typical scenarios are boolean flags or single‑assignment fields where only simple reads/writes occur.
<code style="padding:16px;color:#abb2bf;display:-webkit-box;font-family:Operator Mono,Consolas,Monaco,Menlo,monospace;font-size:12px"><span style="color:#c678dd">volatile</span> boolean shutdownRequested;
public void shutdown() { shutdownRequested = true; }
public void doWork() { while (!shutdownRequested) { /* do stuff */ } }
</code>Another common case is the double‑checked locking singleton pattern, where volatile prevents the instance reference from being observed before the constructor finishes.
<code style="padding:16px;color:#abb2bf;display:-webkit-box;font-family:Operator Mono,Consolas,Monaco,Menlo,monospace;font-size:12px"><span style="color:#c678dd">class</span> Singleton {
<span style="color:#c678dd">private</span> <span style="color:#c678dd">volatile</span> <span style="color:#c678dd">static</span> Singleton instance = <span style="color:#c678dd">null</span>;
<span style="color:#c678dd">private</span> Singleton() {}
<span style="color:#c678dd">public</span> <span style="color:#c678dd">static</span> Singleton getInstance() {
<span style="color:#c678dd">if</span>(instance == <span style="color:#c678dd">null</span>) {
<span style="color:#c678dd">synchronized</span>(Singleton.<span style="color:#c678dd">class</span>) {
<span style="color:#c678dd">if</span>(instance == <span style="color:#c678dd">null</span>)
instance = <span style="color:#c678dd">new</span> Singleton();
}
}
<span style="color:#c678dd">return</span> instance;
}
}
</code>Incorrect Usage
volatiledoes **not** make compound operations atomic. For example, using it for a++ in multiple threads leads to lost updates.
<code style="padding:16px;color:#abb2bf;display:-webkit-box;font-family:Operator Mono,Consolas,Monaco,Menlo,monospace;font-size:12px"><span style="color:#c678dd">public</span> <span style="color:#c678dd">class</span> DontVolatile <span style="color:#c678dd">implements</span> Runnable {
<span style="color:#c678dd">volatile</span> <span style="color:#c678dd">int</span> a;
public static <span style="color:#c678dd">void</span> main(String[] args) <span style="color:#c678dd">throws</span> InterruptedException {
Runnable r = <span style="color:#c678dd">new</span> DontVolatile();
Thread t1 = <span style="color:#c678dd">new</span> Thread(r);
Thread t2 = <span style="color:#c678dd">new</span> Thread(r);
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(((DontVolatile) r).a);
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) { a++; }
}
}
</code>The final value is usually less than 2000 because the increment is not atomic.
synchronized
synchronizedprovides mutual exclusion by acquiring a monitor (mutex) associated with an object. The monitor is implemented in the JVM using native OS mutexes.
When a thread enters a synchronized block, the JVM inserts the bytecode instructions monitorenter and monitorexit around the block.
Instance methods lock on this.
Static methods lock on the Class object.
Explicit synchronized blocks lock on the object specified in the parentheses.
Object Header and Lock States
Every Java object has a header consisting of a MarkWord and a Class Metadata pointer. The MarkWord stores hash code, GC age, and lock information. Depending on contention, the lock can be in one of four states:
No‑lock
Biased lock
Lightweight lock
Heavyweight lock
Lock escalation occurs as contention increases; the lock never downgrades.
The MarkWord changes its bits to reflect the current lock state (biased, lightweight, heavyweight, or unlocked).
Monitor Internals
ObjectMonitor maintains fields such as _owner (the owning thread), _WaitSet (threads waiting on wait()), _EntryList (threads blocked trying to acquire the lock), _recursions (re‑entry count) and count (acquisition count).
Summary
The JVM memory structure is tied to the runtime data areas, while the Java Memory Model governs the semantics of concurrent Java programs. JMM abstracts away hardware‑level details, guaranteeing consistency, atomicity and ordering through memory barriers and eight atomic actions. volatile provides visibility and limited ordering guarantees, whereas synchronized offers full mutual exclusion, visibility, atomicity and ordering. Understanding these mechanisms is essential for writing correct and performant concurrent Java code.
Recommended Reading
Kafka Principles: Diagramming the Architecture
Architecture Design Methodology
Complete Kafka from an Interview Perspective
Database and Cache Write Consistency
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Full-Stack Internet Architecture
Introducing full-stack Internet architecture technologies centered on Java
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
