You are on page 1of 2

Java Memory Model

The Java memory model conceptualizes how the Java virtual machine works with the computer's memory (RAM). It specifies:

how and when different threads can see values written to shared variables by other threads, and
how to synchronize access to shared variables when necessary.

The Internal Java Memory Model

The Java memory model used internally in the JVM divides memory between thread stacks and the heap.

Thread stack Each running thread in the JVM has its own stack that contains its method calls (aka the call stack ). The stack also contains the local variables
for each method being executed. Thus, each thread has its own version of each local variable. All local variables of primitive types ( boolean, byte, short, char,
int, long, float, double) are fully stored on the thread stack.
Heap The heap contains all objects created regardless of what thread created the object. It does not matter if an object was created and assigned to a local
variable, or created as a member variable of another object, the object is still stored on the heap.

So, in essence, the following rules apply:

A local variable of a primitive type resides on the thread stack

A local variable that is a reference to an object resides on the thread stack, but the object itself is stored on the heap.

An object's member variables are stored on the heap along with the object itself - true for members of primitive type as well as reference to another
object.

Static class variables are also stored on the heap along with the class definition.

Any thread can access objects on the heap and by extension the object's member variables. ~ Note: In the diagram below, Object 3 is a shared instance,
i.e., local variables in each thread have a reference to the same object on the heap.~

Hardware Memory Architecture

Modern hardware memory architecture is a bit different from the internal Java memory model and hence it's important to understand the hardware memory
architecture, to understand how the Java memory model works with it.

Cores A modern computer has 2 or more CPUs and a CPU may have multiple cores, which essentially means it is possible to have multiple threads running
simultaneously.

Registers, Cache and Main memory

Each CPU contains a set of registers that are essentially in-CPU memory - the CPU can access (read, write, operate) them much faster than the
main memory.
Each CPU may (almost always) also have a CPU cache memory layer - the CPU can access the cache much faster than the main memory but
slower than the internal registers. In fact most CPUs have multiple cache layers (L1, L2, L3) but that does not concern the JMM.
All CPUs can access the main memory or RAM . It's typically much bigger than the cache memories.

When CPU needs to access the main memory, it will read part of it into the cache and part of the cache into the internal registers before operating on it.

When CPU needs to write the result back to the main memory, it will flush the value from the internal registers into the cache memory and at some point, from the
cache to the main memory.

The CPU cache can have data written-to and flushed-from only a part of its memory at a time, a full cache read or write is not mandatory.

Bridging the Gap

The hardware memory architecture does not distinguish between thread stacks and heap in the JVM - both are located in the same memory hierarchy, i.e., parts of
thread stacks or heap can be lying in either main memory or cache or registers as illustrated below:

We can already see potential issues with this nature of interaction of JVM with the hardware memory architecture. Two major ones are:

1. Visibility of thread updates (writes) to shared variables.


2. Race conditions when reading, evaluating and writing shared variables.

Visibility of shared objects Imagine two or more threads sharing an object stored in the main memory initially.

Say, a thread running on one CPU reads the object into its cache, makes changes and writes the changes back into the cache. As long as this write is not
flushed into the main memory, the changes are not visible to threads running on other CPUs. This may lead to the presence of multiple copies of the shared
object on different CPU caches. Volatile This problem can be solved using Java's volatile keyword as a modifier for the shared variable. The volatile
keyword ensures the variable is always read from the main memory and written back to it in case of an update.
Race conditions Two or more threads trying to modify the state of a shared object (or modify the value in case of a primitive), race conditions occur.

Imagine two threads running on different CPUs trying to increment the value of count of a shared object - both of them end up incrementing the value in their
respective cache memories concurrently (without proper synchronization) which when written back into the main memory yeild a value of count + 1 instead of
count + 2 had the increment been done sequentially. Even in case of a single CPU, such an operation is not atomic. Hence two threads on the same core may
interleave while incrementing the value of a shared variable yielding indeterminate outputs. Synchronization Race conditions can be solved using
synchronized blocks. A synchronized block guards the critical section of the code ensuring that only one thread can enter the block at any given time.
Synchronization also guartantees that all variables will be read from the main memory when they are accessed inside the block and written back into the
main memory when the thread exits regardless of whether they are volatile or not.

== End of section ==

You might also like