Unauthorized republication of this article is prohibited

  • 1. Why an in-memory model?
    • 1.1. Hardware memory architecture
    • 1.2. Cache consistency issues
    • 1.3. Processor optimization and instruction reordering
  • 2. Concurrent programming problems
  • 3. Java memory model
    • 3.1. Relationship between Java runtime memory region and hardware memory
    • 3.2. Relationship between Java threads and main memory
    • 3.3. Inter-thread communication
  • 4. Summarize with attitude

Interviewers often ask, “What is the Java Memory Model (JMM)?”

Java memory is divided into five main blocks: heap, method area, virtual machine stack, local method stack, PC register, Balabala…

The interviewer smiled knowingly, revealing a ray of light: “Ok, today’s interview is over, go back and wait for the notice.”

Generally hear such notice this sentence, the interview probability is cool cool. Why is that? Because the interviewer got the concept wrong, the interviewer wanted to investigate JMM, but the interviewer started reciting the eight-part essay as soon as he heard the key words Java memory. There’s a big difference between the Java Memory model (JMM) and the Java runtime memory area. Don’t walk away. Keep reading.

1. Why an in-memory model?

To answer this question, we need to understand the memory architecture of traditional computer hardware. All right, I’m going to start drawing.

1.1. Hardware memory architecture

(1) the CPU

Those of you who have been to the computer room know that a large server is usually equipped with multiple cpus and each CPU has multiple cores, which means that multiple cpus or cores can work concurrently (concurrently). If you start a multithreaded task in Java, it’s likely that each CPU will run one thread, and at some point your task will truly be executing concurrently.

(2) CPU Register

A CPU Register is a CPU Register. CPU registers are integrated within the CPU and perform operations on registers several orders of magnitude more efficiently than on main memory.

(3) CPU Cache Memory

CPU Cache Memory is also the CPU Cache, which can also be L2 level Cache as opposed to register. The efficiency of memory reading is very high compared to the speed of hard disk reading, but it is still an order of magnitude different from that of CPU. Therefore, multi-level caching is introduced between CPU and main memory for the purpose of buffering.

(4) Main Memory

Main Memory is Main Memory, which is much larger than L1 and L2 caches.

Note: Some high-end machines also have L3 level caches.

1.2. Cache consistency issues

Because there is an order of magnitude gap between main memory and CPU processor’s computing capacity, the traditional computer memory architecture will introduce cache to serve as the buffer between main memory and processor. The CPU will put the commonly used data in the cache, and the CPU will synchronize the calculation results to main memory after the calculation.

Using caching solves the problem of CPU and main memory rate mismatch, but introduces another new problem: cache consistency.In a multi-CPU system (or a single-CPU, multi-core system), each CPU core has its own cache and they share the same Main Memory. If multiple cpus work on the same main memory area, the cpus will read data into the cache for calculation. This may cause data inconsistency in the caches.

Therefore, each CPU must follow a certain protocol when accessing the cache and perform operations according to the protocol when reading and writing data to maintain the consistency of the cache. Such protocols include MSI, MESI, MOSI, and Dragon Protocol.

1.3. Processor optimization and instruction reordering

Caches are added between the CPU and main memory to improve performance, but cache consistency issues can be encountered in multi-threaded concurrent scenarios. Is there any way to further improve CPU performance? The answer is: processor optimization.

In order to maximize the utilization of the arithmetic unit inside the processor, the processor will execute the input code out of order, which is processor optimization.

In addition to processor optimizations, compilers in many modern programming languages do similar optimizations, such as just-in-time compilers (JIts) in Java that do instruction reordering.

Processor optimization is also a type of reordering, which can be divided into three types:

  • Compiler optimized reordering. The compiler can rearrange the execution order of statements without changing the semantics of a single-threaded program.
  • Instruction – level parallel reordering. Modern processors use instruction-level parallelism to superimpose multiple instructions. If there is no data dependency, the processor can change the execution order of the machine instructions corresponding to the statement.
  • Memory system reordering. Because the processor uses caching and read/write buffers, this makes it appear that load and store operations may be performed out of order.

2. Concurrent programming problems

It’s a bunch of hardware stuff, and some of you might be a little confused, but what does it have to do with the Java memory model? Don’t worry. Let’s take our time.

Those of you familiar with Java concurrency will be familiar with these three problems: “visibility problems”, “atomicity problems”, and “orderliness problems”. If we look at these three problems from a deeper level, they are actually caused by “cache consistency”, “processor optimization” and “instruction reordering” mentioned above.

Cache consistency issues are visibility issues, processor optimization can cause atomicity issues, and instruction reordering can cause ordering issues.

When something goes wrong, it has to be fixed, so what can be done? First of all, I came up with a simple and rude solution: eliminating the cache and allowing the CPU to interact directly with main memory solved the visibility problem, and prohibiting processor optimization and instruction reordering solved the atomicity and orderliness problem, but such a night back to the pre-release, obviously not desirable.

So the technical predecessors came up with the idea of defining a set of memory models on physical machines to regulate the read and write operations of memory. The memory model solves the concurrency problem in two main ways: limiting processor optimization and using memory barriers.

3. Java memory model

The same set of memory model specifications may be implemented differently by different languages. Next, I will focus on the implementation principle of the Java memory model.

3.1. Relationship between Java runtime memory region and hardware memory

For those of you who are familiar with the JVM, the JVM runtime memory area is fragmented, divided into stacks, heaps, etc. These are the logical concepts defined by the JVM. In the traditional hardware memory architecture, there is no such thing as stack and heap.As you can see from the diagram, the stack and heap exist in both the cache and main memory, so the two are not directly related.

3.2. Relationship between Java threads and main memory

The Java memory model is a specification that defines many things:

  • All variables are stored in Main Memory.
  • Each thread has a private Local Memory where it stores copies of shared variables to read/write.
  • All operations by a thread on a variable must be done in local memory rather than directly reading or writing to main memory.
  • Different threads cannot directly access variables in each other’s local memory.

Reading the text was too boring, so I drew another picture:

3.3. Inter-thread communication

If two threads operate on a shared variable, the shared variable starts at 1, and each thread increments the variable by 1, the expected value of the shared variable is 3. There are a number of operations under the JMM specification.To better control the interaction between main and local memory, the Java memory model defines eight operations to implement:

  • Lock: Lock. A variable acting on main memory that identifies a variable as a thread-exclusive state.
  • Unlock: Unlocks the device. To release a locked variable from a main memory variable before it can be locked by other threads.
  • Read: reads data. Act on a main memory variable to transfer a variable value from main memory to the thread’s working memory for subsequent load action
  • Load: Loads. A variable that operates on working memory and puts the value of the variable obtained from main memory by the read operation into a copy of the variable in working memory.
  • Use: use. A variable that operates on working memory and passes the value of a variable in working memory to the execution engine. This operation is performed whenever the virtual machine reaches a bytecode instruction that requires the value of the variable.
  • Assign: assigns a value. A working memory variable that assigns a value received from the execution engine to the working memory variable, performing this operation whenever the virtual machine accesses a bytecode instruction that assigns a value to the variable.
  • Store: stores. A variable acting on working memory that transfers the value of a variable in working memory to main memory for subsequent write operations.
  • Write: Writes data. A variable operating on main memory that transfers store operations from the value of a variable in working memory to a variable in main memory.

Note: Working memory also means local memory.

4. Summarize with attitude

Since there is an order of magnitude rate difference between the CPU and the main memory, the traditional hardware memory architecture that introduces multi-level cache is considered to solve the problem. Multi-level cache acts as a buffer between the CPU and the main internal memory to improve the overall performance. It solves the problem of speed difference, but also brings the problem of cache consistency.

Data exists in both the cache and main memory, and if left unregulated is a recipe for disaster, the memory model has been abstracted from traditional machines.

The Java language introduced the JMM specification based on the memory model, aiming to solve the problems caused by the inconsistency of local memory data, the reordering of code instructions by the compiler, and the out-of-order execution of code by the processor when multithreading communicates through shared memory.

To more precisely control the interaction between working memory and main memory, the JMM defines eight operations: Lock, unlock, read, Load, Use, assign, Store, and write.

— End —

There are many things about the Java memory model that have not been covered, such as memory barriers, happens-before, locking mechanisms, CAS, and so on. Want liver a series, come on!

Author: Lei Xiaoshuai

Recommend a Github open source project, “Java eight Part essay” Java interview routine, Java advanced learning, break the volume of big factory Offer, promotion and salary! Github.com/CoderLeixia…

About the author: ☕ Read several books: Huazhong University of Science and Technology master degree; 😂 wave over several big factories: Huawei, netease, Baidu…… 😘 has always believed that technology can change the world, is willing to maintain the original heart, refueling technicians!

Search the official account “Architect who Loves to laugh” on wechat and follow this interesting worker with pursuit of technology.