#[Java memory model]

Cough cough cough, look at the end of all the people…

Internally, the Java virtual machine uses the JMM(Java Memory Model) to divide memory into two logical units, the thread stack (or local memory) and the heap.

Each thread has its own thread stack, which holds local variables (also called local variables), parameters defined in methods, and parameters of the exception handler (catch parameters). These parameters and variables are thread-local operations and are isolated, so they are not affected by the memory model. [Obtaining resources]

Visibility problem

Suppose we have the following set of code:

static boolean run = true; public static void main(String[] args) throws InterruptedException { Thread t = new Thread(()->{ while(run){ // .... }}); t.start(); sleep(1); run = false; // thread t will not stop as expected}Copy the code

Here are the execution steps:

  • When thread T starts executing, it reads a copy of run from the heap and saves it to its own thread stack (creating a copy of run in its own thread stack). Note: This is done to reduce access to runs in the heap and improve efficiency.
  • Every time thread T reads run, it reads it from its own thread stack.
  • After a second, the main thread changes the value of run and synchronizes it to the heap, while T reads the value of run from its own stack. The value of run in the stack hasn’t changed, so the thread can’t stop.

It boils down to this: another thread has modified the data in the heap, and no other thread can sense that the data has been modified.

Here is an example of an object distributed in memory:

public class MyRunnable implements Runnable() { public void run() { methodOne(); } public void methodOne() { int localVariable1 = 45; MySharedObject localVariable2 = MySharedObject.sharedInstance; / /... do more with local variables. methodTwo(); } public void methodTwo() { Integer localVariable1 = new Integer(99); / /... do more with local variable. } } 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

Each thread executing methodOne() creates its own copy of localVariable1 and localVariable2 on its own thread stack; Copies of localVariable1 will be completely separate from each other, living only on the thread stack for each thread; One thread cannot see what changes another thread has made to localVariable1, that is, it is not visible to other threads (even if one thread synchronizes the changed value back to the heap and the other thread does not re-read it), nor is it affected by the memory model.

LocalVariable2 also has different copies in different thread stacks, but both copies end up pointing to the object referenced by the static variable (the same object on the heap); Thus, both copies of localVariable2 end up pointing to the MySharedObject instance that the static variable points to. MySharedObject instances are also stored on the heap. It corresponds to Object 3 in the figure below.

Note: The MySharedObject class also contains four member variables that are stored on the heap with objects, two of which point to two other Integer objects corresponding to Object 2 and Object 4 in the figure above. [Obtaining resources]

A local variable named localVariable1 is created in the methodTwo() method. This local variable is a reference to the Integer object, and the localVariable1 reference stores a copy in each thread that executes methodTwo(); But because this method creates a new Integer Object each time it executes, the localVariable1 reference points to a new Integer instance, corresponding to Object 1 and Object 5 in the figure below. [Obtaining resources]

It is worth noting: For basic data types (Boolean, byte, short, char, int, long, float, double), store a copy of the variable directly to the thread stack. For reference types (Byte, Integer, Long, etc.), the reference (that is, the variable name) is stored in its own thread stack, and the object itself is stored in the heap. In addition, the heap holds all the instance fields (also called instance fields), static fields (also called static fields), and array elements (which are also objects) that are shared by multiple threads in the heap.

Finally, note that when class variables (also known as static variables, static fields) and instance variables (also known as instance fields, non-static fields) are called in a method, copies of these variables are also saved to the thread stack.

Resolve visibility issues

The simplest way to do this is to use the volatile modifier and change the code to volatile static Boolean run = true; So that thread T gets the value from the heap every time it uses it.

Note that this keyword can be used to modify member variables as well as static member variables.

You can also use synchronized for visibility.

reorder

To improve program performance, the CPU and JIT compiler do instruction reordering (that is, generate machine instructions that are not in the same order as bytecode instructions) without affecting the results of single-threaded execution.

This is an example of reordering in a single thread, assuming the following code:

a = b + c
d = a + e

l = m + n
y = x + z

Copy the code

After reordering, it might look like this: since the first three lines have no correlation (correlation means dependencies between data, for example, line 6 depends on line 1, so they are correlated), the CPU might execute the first three lines in parallel to improve program performance. [Obtaining resources]

a = b + c

l = m + n
y = x + z

d = a + e

Copy the code

Using a graph to show the multi-core CPU reordering and instruction execution:

As you can see from the above picture, CPU execution instructions are not random, just like reordering must follow a certain rule; This rule is the as-if-serial semantics: all actions can be reordered for optimization, but their reordered result must be the same as the result of the program code itself. The Java compiler, runtime, and processor all guarantee as-IF-Serial semantics for a single thread. For example, to preserve this semantics, reordering does not occur in data-dependent operations.

The impact of reordering on multithreading

Take a look at this code first:

public class PossibleReordering { static int x = 0, y = 0; static int a = 0, b = 0; public static void main(String[] args) throws InterruptedException { Thread one = new Thread(new Runnable() { public void run() { a = 1; x = b; }}); Thread other = new Thread(new Runnable() { public void run() { b = 1; y = a; }}); one.start(); other.start(); one.join(); other.join(); System.out.println(" (" + x + ", "+ y +") "); }Copy the code

It is easy to imagine that the result of this code could be (1,0), (0,1), or (1,1), because thread one could finish executing before thread two starts, or vice versa, or even if the instructions are executed simultaneously or alternately.

However, the result of this code execution may also be (0,0). This is because, at actual runtime, code instructions may not be executed strictly in the order of code statements. The statement execution process that results in (0,0) is shown in the figure below. It should be noted that the assignment of a=1 and x=b has been reversed, or that the order has been reordered. (The fact that this result is printed does not necessarily mean that instruction reordering has occurred, as memory visibility issues can also cause this output.)

Happens-before relations

The Java definition of happens-before is that if the result of one operation needs to be visible to another, there must be a happens-before relationship between the two operations. That is, the two operations before and after happens-before are not reordered and the latter is visible to the former’s memory. It is important to note that the two operations described here can be within a thread or between different threads. Happens-before only requires that the results of the first operation be visible to the second operation and that the first operation precedes the second operation. To put it simply: happens-before is the visibility rule; When a write to a shared variable can be visible to a read of a shared variable.

The following happens-before rules can be summarized as specified in the Java memory model:

  • Code execution order in A single thread: A happens-before B. The previous operation (variable assignment) is visible to the subsequent operation (variable value) (that is, the value of the variable is visible).
  • Synchronized: the first thread releases the lock, which is visible to the second thread. Note: Assuming the second thread does not acquire the lock and uses the shared variable directly, this will not work (not visible).
  • Volatile: Writes on the first thread are visible to reads on the second thread. Like a thread is reading and writing directly to the heap.
  • The thread starts.
  • Thread termination: It is not visible to the calling Thread until another Thread has called Thread.join (until this method returns) or thread. isAlive (which must return false).
  • Interrupt: After one thread Thread#interrupt() another thread, the value of the shared variable is visible to the interrupted thread.
  • Finalizer: The end of an object’s constructor happens-before the start of the object’s finalizer.
  • Transitive: if A happens-before B, and B happens-before C, A happens-before C.

The happens-before relationship is an approximate description of the Java memory model. It is not rigorous, but it is useful for reference in daily program development. For a more rigorous definition and description of the Java memory model, please refer to the original JSR-133 or section 17.4 of the Java Language Specification.

In addition, the Java memory model extends the semantics of volatile and final. The extension to the semantics of volatile ensures that volatile variables are not reordered in some cases, and that reads and assignments to volatile 64-bit variables double and long are atomic. The extension to the final semantics ensures that all final member variables must be initialized before an object’s build method ends (provided that no this reference overflows).

The relationship between happens-before and JMM is shown below:

The JMM, depending on happens-before, disallows the processor from reordering certain code and guarantees visibility of shared variables; For details on Java memory model reordering and the volatile keyword, see the volatile keyword.

Discussion – Why are fields and tuples stored in the heap?

Objects in Java are compound objects, consisting of fields or arrays of Object + primitive types.

What is used in the program and what is seen by the CPU is data, which is obtained from the physical address plus offsets.

conclusion

Thread stacks and heaps, happens-before, reordering, and Memory barriers are all part of the Java Memory Model.

Knowledge expansion – Atomicity

Atomic, “atom,” means indivisible.

That is, when multiple threads operate on the same shared variable, they cannot interoperate; For example, the T1 thread does something, and then the T2 thread does something, so there’s no way to guarantee atomicity; So you have to wait for one thread to finish before the other thread can continue.

Reordering in Java exception handling

To ensure as-IF-Serial semantics, the Java exception handling mechanism also does some special handling for reordering.

For example, in the following code, y = 0/0 May be reordered before x = 2. To ensure that x = 1 is not output incorrectly, the JIT inserts error compensation code into the catch statement during reordering, assigning x to 2. Restore the program to the state it should have been in when an exception occurred.

This does complicate the logic of exception catching, but the principle of JIT optimization is to optimize the logic of the code in normal operation, even at the expense of the complexity of the catch block, which is an “exception” condition. [Obtaining resources]

public class Reordering { public static void main(String[] args) { int x, y; x = 1; try { x = 2; y = 0 / 0; } catch (Exception e) { } finally { System.out.println("x = " + x); }}}Copy the code

Knowledge expansion – Memory system reordering

To improve performance, compilers and processors often reorder instructions when executing programs. There are three types of reordering:

  • Compiler optimized reordering: The compiler can rearrange the execution order of statements without changing the semantics of a single-threaded program.
  • Reordering instruction sets in line: Processors now use instruction set parallelism to overlap multiple instructions. If there is no data dependency, the processor can change the execution order of the machine instructions corresponding to the statement.
  • Reordering of memory systems: Since the processor uses caching and read/write buffers, it can appear that load and store operations are performed out of order.

The first two have been written about in this article, and the third is explained in detail here.

In order to avoid the time cost of accessing the main memory as much as possible, most processors use cache to improve performance. Its model is shown in the figure below:

Under this model, there is a phenomenon that the data in the cache is not synchronized with the data in main memory, and the data in the cache between cpus (or CPU cores) is not synchronized in real time.

As a result, cpus may see different values for the same memory location at the same point in time. From a program’s point of view, the values of shared variables seen by different threads may differ at the same point in time.

Some view this phenomenon as a kind of reordering, named “memory system reordering.” The result of this memory visibility problem is that memory access instructions have been reordered. [Obtaining resources]

The order of instructions from the Java source code to the actual execution goes through one of the following three reorders:

If you can, please give me a three support me?????? [Obtain information]