NOTE
6.23 Revisiting the JMM
After understanding the underlying computer architecture, revisit why the Java Memory Model is needed, what it defines, and happens-before.
This is a historical learning note and may contain outdated or incomplete understanding.
After understanding some low-level computer knowledge, the JMM becomes much easier to understand.
1. Why Is the JMM Needed?
As mentioned earlier, a memory model specifies which execution orders among all possible orders of a program’s memory operations (reads and writes) are correct.
Different processor architectures have different memory models. Java is a cross-platform language across operating systems and hardware, so in order to hide these underlying differences, Java defines its own memory model: the JMM.
2. What Is the JMM?
The JMM describes how multiple threads in Java interact through memory. It also describes the semantics of single-threaded execution.
2.1. Definition of the JMM
Baidu Baike explains it as follows:
The Java Language Specification mentions that the JVM has a main-memory area (Main Memory or Java Heap Memory). All variables in Java exist in main memory and are shared by all threads. Each thread also has its own working memory. Working memory stores copies of some variables from main memory. A thread’s operations on variables do not occur directly in main memory, but in working memory. Threads cannot directly access each other’s working memory, and variable transfer in a program depends on main memory. On multi-core processors, most data is stored in cache, and if cache contents are not propagated through memory this is also a form of invisibility. In Java programs, memory itself is a relatively expensive resource. In fact, memory is an expensive resource not only for Java applications but for operating systems as well. There are several typical controllable sources of performance overhead in Java programs. The memory-model visibility guarantees provided by the
synchronizedandvolatilekeywords use special memory-barrier instructions to flush caches, invalidate caches, flush hardware write buffers, and delay instruction propagation. This mechanism inevitably has some impact on Java-program performance.
The read/write relationship between threads and main memory is shown below:


2.2. Revisit How the JMM Explains Multithreaded Memory Operations
2.2.1. Visibility
- Explanation: when Thread A changes a shared variable in main memory, when can other threads observe that update?
- Cause: each thread has working memory that caches data from main memory.
- Answer: explained by happens-before.
2.2.2. Ordering
- Explanation: when Thread A updates multiple shared variables, in what order do other threads observe those changes?
- Cause: processor reordering and compiler reordering.
- Answer: explained by happens-before.
2.2.3. Atomicity
- Explanation: when Thread A executes a compound operation, can other threads observe an intermediate state?
- Cause: the CPU guarantees atomicity only for basic read, modify, and write operations; guaranteeing atomicity for compound operations would be too expensive.
- Answer: reads and writes of primitive types are atomic except for
longanddoubleunder the historical model described by this note.
3. How Programmers Should Understand the JMM
The JMM can also be considered “low-level” in a sense. During development, we only need to understand the happens-before rules that the JMM exposes to programmers.
3.1. happens-before
If A happens-before B, then the result of operation A is visible to B.
3.2. Explanation
This does not mean that A must execute before B in wall-clock time. It means that if A executes before B, then the effects of all writes before A are visible to operations after B according to the happens-before relationship.
3.3. Example
An unlock operation on a monitor happens-before a subsequent lock operation on the same monitor.
This does not mean that “unlock” conceptually must always happen before “lock.” It means that the effects of all writes before the unlock are visible to all reads after the subsequent lock.
3.4. Common happens-before Rules
- Program order
Within one thread, operations follow program-order semantics. This is related to as-if-serial.
Of course, if there is no data dependency, instructions may actually be reordered.
- Intrinsic locks
Thread A’s release of lock X happens-before Thread B subsequently acquires lock X.
- volatile
A write to a volatile variable happens-before a subsequent read of that same volatile variable.
- Thread start
A call to Thread.start happens-before any action in the started thread.
- Thread termination
A thread’s actions happen-before another thread successfully returns from Thread.join on it.
3.4.1. Transitivity of happens-before
happens-before is transitive, which allows many additional rules to be derived.
4. JSR 133
JSR 133 was proposed to address defects in the memory model before JDK 1.5.
Defects before JDK 1.5 included:
- allowing one thread to first observe the default value of a
finalfield and then observe its initialized value, meaning afinalfield could effectively appear to change; - allowing
volatilewrites to be reordered with other non-volatilereads and writes.
5. References
- Java memory model - Wikipedia
- Breaking Down Concurrency (11): Memory Model Reordering - Juejin
- In-Depth Analysis of the Java Memory Model (JMM) - Juejin
- If Someone Asks You What the Java Memory Model Is Again, Send Them This Article - HollisChuang’s Blog
- In-Depth Java Memory Model Series | ifeve.com
- Java Memory Model - Baidu Baike
- Java Memory Model Pragmatics (transcript)
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub