The Java Memory model shows how the Java virtual machine interacts with computer memory to solve the problem of resource access when multiple threads read and write shared memory.

The memory model

The memory model in the Java virtual machine separates the thread stack from the heap. The following diagram depicts the logic of the Java memory model.

Each thread has its own thread stack, which contains information about the methods that the thread called when it executed to the current location. As the thread executes code, the thread stack continuously performs loading and unloading operations.

All variables defined in the called method are stored in the thread stack and are accessed by the thread itself, invisible to other threads. Even if two threads execute the same code, variables are created repeatedly in the thread’s own stack. One thread may pass copies of variables to another thread, but cannot share the variables themselves.

Variables are also stored differently on the stack. Variables of the basic types (int, byte, Long, Boolean, char, double, float, short) are stored directly on the stack, while values of other types are stored in the heap, and only Pointers to the addresses of variables in the heap are kept in the thread stack.

The heap stores all objects created in a Java program, regardless of the thread. It doesn’t matter if you create an object and assign it to a local variable, or if you create it as a member variable of another object, which is still stored in the heap.

It is worth noting that static class variables in Java are also stored in the heap as the class is initialized.

Any thread that has a pointer to an object can access an object on the heap. When a thread can access an object, it can also access its member variables. If two threads call a method on the same object at the same time, they will both have access to the object’s member variables, but each thread will have its own copy of local variables.

Both threads have a set of local variables that point to shared objects on the heap. These two threads have different Pointers to the same object. Their Pointers are also local variables and therefore stored in the thread stack for each thread (on each thread). However, two different Pointers point to the same object on the heap.

The following code block is an example of the figure above in action.

public class MyRunnable implements Runnable(a) {

    public void run(a) {
        methodOne();
    }

    public void methodOne(a) {
        int localVariable1 = 45;
        MySharedObject localVariable2 = MySharedObject.sharedInstance;
        / /...
        methodTwo();
    }

    public void methodTwo(a) {
        Integer localVariable1 = new Integer(99);
        / /...}}Copy the code
public class MySharedObject {

    //static variable pointing to instance of MySharedObject
    public static final MySharedObject sharedInstance = new MySharedObject();

    //member variables pointing to two objects on the heap
    public Integer object2 = new Integer(22);
    public Integer object4 = new Integer(44);

    public long member1 = 12345;
    public long member2 = 67890;
}
Copy the code

Hardware architecture

The memory architecture of modern hardware is somewhat different from that of the Java memory model, and understanding the hardware architecture is also helpful in understanding the Java memory model. The simple hardware architecture diagram is as follows:

Modern computers tend to have multi-core cpus, usually more than one CPU, so it is physically possible for multiple threads to run concurrently. This means that if the Java application is multithreaded, each CPU may run one thread simultaneously (concurrently) in the Java application.

Each CPU contains a set of registers, which are essentially in-CPU memory. The CPU can perform operations on these registers much faster than it can on variables in main memory, because the CPU can access these registers much faster than it can access main memory.

Each CPU may also have one CPU cache. In fact, most modern cpus have a cache of some size. A CPU can access its cache faster than its main memory, but generally not as fast as it can access its internal registers. Thus, the CPU cache memory is located between the speed of the internal register and the main memory. Some cpus may have multiple Cache layers (L1 and L2 caches). It is not important to understand how the Java memory model interacts with memory, but rather to know that the CPU can have some kind of caching layer.

The computer also contains a main storage area (RAM). All cpus have access to the main memory. The main storage area is usually much larger than the CPU’s cache.

Typically, when the CPU needs to access main memory, it reads part of main memory into its CPU cache. It can even read a portion of the cache into its internal register and perform operations on it. When the CPU needs to write the result back to main memory, it will flush the value from its internal registers to the cache, and then flush the value back to main memory at some point.

When the CPU needs to store something else in the cache, it will typically flush the values stored in the cache back to main storage. The CPU cache can write data to part of its memory at once and flush part of its memory at once. It doesn’t have to read/write the full cache with every update. Typically, updates are cached in smaller blocks of storage called “cache rows,” which can read one or more cache rows into cache storage, and which can be flushed back to main storage again.

The Java memory model is associated with hardware

As mentioned earlier, the Java memory model is different from the hardware memory architecture, which does not distinguish between thread stacks and heaps. On hardware, thread stacks and heaps are located in main memory. Parts of the thread stack and heap may sometimes appear in the CPU cache and internal CPU registers. The following illustration illustrates this:

Some problems can occur when objects and variables can be stored in a variety of different storage areas of the computer. The two main issues are:

  • Visibility of thread updates (writes) to shared variables.
  • Race conditions for reading, checking, and writing shared variables.

Visibility of objects

If two or more threads share an object and the volatile keyword is not used correctly, updates made to the shared object by one thread may not be visible to other threads.

Each thread can have its own copy of the shared library, each in a different CPU cache. Imagine that shared objects are originally stored in main storage. A thread running on the CPU then reads the shared object into its CPU cache and makes modifications. Threads running on other cpus cannot see the changed version of the shared object as long as the CPU cache is not flushed back to main storage.

The following figure illustrates this situation, where a thread running on the left CPU copies the shared object into its CPU cache and changes its count variable to 2. Other threads running on the right CPU cannot see this change because the count update has not been written back to main memory.

Of course, this problem can be solved with the volatile keyword.

Competitive conditions

If two or more threads share an object, and more than one thread updates variables in that shared object, a race condition can occur.

Suppose thread A reads the variable count of the shared object into its CPU cache, and thread B does the same, but in A different CPU cache. Now, thread A adds one to count, and thread B does the same. Count has now been increased twice, once per CPU cache.

If these increments are performed sequentially, the variable count is incremented twice and the original value +2 is written back to main memory.

However, these two deltas are executed concurrently without synchronization. No matter which thread in threads A and B writes its updated version back to main memory, the updated value is only 1 higher than the original value, despite two increments.

The figure illustrates the occurrence of the problem of competitive conditions as described above:

This problem can be solved using the synchronized keyword.