Java memory model

Source: Internet
Author: User

Overview

Measuring the quality of a Service's performance, the number of transactions per second (transactions per Second,tps) is one of the most important indicators, which represents the total number of requests that the server can respond to on average in a second, and the TPS value is closely related to the concurrency of the Program.

The Java memory model (model,jmm) is used to mask the memory access differences between various hardware and operating systems to enable Java programs to achieve consistent memory access across all PLATFORMS. The main goal of the Java memory model is to define the access rules for each variable in the program, that is, the underlying details of storing variables in the virtual machine into memory and removing variables from memory.

C + +: A memory model that uses physical hardware and operating systems DIRECTLY.


The efficiency and consistency of the hardware

The vast majority of computing tasks can not be done only by the processor "compute", the processor at least to interact with the memory, such as reading speed, storage operation results, and so on, this I/O operation is difficult to eliminate (can not rely on registers to complete all the operational tasks). Because the Computer's storage device (slow) and processor operation Speed (fast) There are several orders of magnitude difference, therefore, modern computer systems have to add a layer of read and write speeds as close as possible to the processor speed cache (cache) as a buffer between memory and processor: the data needed to copy the operation to the cache, so that the operation can be done quickly, when the operation is finished and then back to memory from the cache, This way the processor does not have to wait for slow memory to read and Write.

Cache Coherence: in a multiprocessor system, each processor has its own cache, and they share the same main memory, and when the operations of multiple processors involve the same block of main memory, they may cause their cache data to be Inconsistent. If this is the case, whose cache data will be synchronized back to the main memory? In order to resolve the consistency problem, the need for each processor to access the cache is followed by a number of protocols to operate according to the protocol when reading and writing, such as msi, MESI (illinois Protocol), MOSI, Synapse, firefly, and Dragon Protocol and so On. The term "memory model" can be understood as the abstraction of Read-write access to a particular memory or cache under a specific operating Protocol.


The interaction between the processor, cache, and main memory:


In addition to increasing the cache, in order to make the operating unit within the processor as fully utilized as possible, the processor may perform random execution (out-of-order Execution) optimization of the input code, and the processor will reorganize the results of the disorderly execution after the Calculation. Ensure that the result is consistent with the sequential execution, but there is no guarantee that the order of each statement in the program is consistent with the order in the input code, so if there is a calculation task that relies on the result of another computational task, the order is not guaranteed by the order of the Code. Similar to the Processor's unordered execution optimizations, the Java virtual Machine's Instant compiler also has similar command reordering (instruction Reorder) optimizations.


Main memory vs. working memory

The main goal of the Java memory model is to define the access rules for each variable in the program, that is, the underlying details of storing variables in the virtual machine into memory and removing variables from memory. The variables here (Variables) include instance fields, static fields, and elements that construct array objects, but do not include local variables and method parameters, because the latter is thread-private and will not be shared, and naturally there will be no competition issues.

The Java memory model specifies that all variables are stored in the main memory (main memories), that each thread has its own working memory (working memories), that the Thread's working memory holds a copy of the main memory of the variable used by the thread, and that all the thread-to-variable operations (read, assignments, Etc.) must be done in working memory and not directly read and write to the variables in main memory. There is no direct access to variables in the Other's working memory between different threads, and the transfer of variable values between threads needs to be done through main memory.

Here the main memory, working memory and Java memory Area Java heap, stack, method area and so on is not the same level of memory division, the two basically no relationship, if must reluctantly correspond: main memory corresponds to the heap, working memory corresponds to the STACK. From a lower level, the main memory directly corresponds to the memory of the physical hardware, and for better running speed, the virtual machine (or even the optimization of the hardware system Itself) may make the working memory priority stored in the register and cache, because the program runs the main access to read and write the working memory.

The interaction between threads, main memory, and working memory:



Inter-memory Interaction Operations

1) lock: A variable that acts on the main memory, which identifies a variable as a thread-exclusive State.

2) unlock (unlocked): A variable that acts on the main memory, releasing a variable that is in a locked state, and the released variable can be locked by another thread.

3) read: A variable acting on the main memory, which transfers the value of a variable from main memory to the working memory of the thread for subsequent load actions to Use.

4) load (load): A variable that acts on the working memory, which places the value of a read operation from the main memory into a variable copy of the working memory.

5) use: A variable acting on the working memory that passes the value of a variable in the working memory to the execution engine, which is performed whenever the virtual opportunity is to a bytecode instruction that needs to use the value of the Variable.

6) assign (assignment): A variable that acts on the working memory, assigning a variable that is received from the execution engine to the working memory, and performs this operation whenever the virtual opportunity is assigned to a byte-code instruction that assigns a value to a variable.

7) store: A variable that acts on the working memory, which transmits the value of a variable in the working memory to the main memory for subsequent write operations to Use.

8) write: A variable that acts on the main memory, which puts the value of the variable that the store operation obtains from the working memory into a variable in the main memory.


If you want to copy a variable from main memory to working memory, then perform the read and load operations sequentially, and if you want to synchronize a variable from the working memory back to the main memory, execute the store and write operations sequentially. Note: the Java memory model requires that both operations must be executed sequentially, without guarantee of continuous execution. In addition, it must meet:

1) does not allow one of the read and load, store, and write operations to appear separately, that is, a variable is not allowed to read from the main memory but the working memory is not accepted, or from the working memory initiated writeback but the main memory is not acceptable.

2) does not allow a thread to discard its most recent assign operation, that is, the variable must be synchronized back to main memory after it has changed in working memory.

3) a thread is not allowed to synchronize data from the working memory of the thread back to main memory for no reason (no assign operation has occurred).

4) a new variable can only be "born" in main memory, does not allow direct use of a variable that is not initialized (load or Assign) in working memory, in other words, the assign and load operations must be performed before a variable is implemented as a use, store Operation.

5) A variable is allowed only one thread to lock it at the same time, but the lock operation can be repeated multiple times by the same thread, and after performing the lock multiple times, the variable will be unlocked only if the same number of unlock operations are Performed.

6) If a lock operation is performed on a variable, it will empty the value of this variable in the working memory, and the load or assign operation initializes the value of the variable before the execution engine uses the Variable.

7) If a variable is not locked by the lock operation beforehand, It is not allowed to perform a unlock operation on it, nor is it allowed to unlock a variable that is locked by another thread.

8) before performing a unlock operation on a variable, this variable must be synchronized back to main memory (execution store, write operation).


Special rules for volatile types of variables

When a variable is defined as volatile, it has two characteristics:

1) Ensure that this variable is visible to all threads; the normal variable (not being modified by Volatile) does not do this, and the value of the normal variable is passed through the main memory to the THREAD.

2) volatile variables are not thread-safe, and volatile variables do not have a consistency problem in the working memory of each thread (in the working memory of each thread, volatile variables can also be inconsistent, but because they are refreshed before each use, The execution engine does not see the inconsistency, so there is no consistency problem, but the operation inside Java is not atomic, and the operation of the volatile variable is as unsafe as it is in Concurrency.


For example: public static volatile int race = 0; race++, multithreaded execution is unsafe: when the getstatic instruction takes the value of race to the top of the operation stack, the volatile keyword guarantees that the value of race is correct at this time, but when executing iconst_1, iadd these instructions, Other threads may have increased the value of the race, and the value at the top of the stack has become stale data, so the putstatic instruction may have synchronized the smaller race values back into main memory.


Even if compiled with only one bytecode instruction, it does not mean that executing this instruction is an atomic Operation.

A bytecode instruction when interpreting execution, the interpreter will run many lines of code to implement its semantics, and if it is compiled, a single bytecode instruction may be translated into several native machine code instructions. Use the-xx:+printassembly parameter output disassembly to analyze more rigorously.


Because volatile variables can only guarantee visibility, in an operation scenario that does not conform to the following two rules, it is necessary to ensure atomicity by locking (atomic class in synchronized or java.util.concurrent):

1) The result of the operation does not depend on the current value of the variable, or can ensure that only a single thread modifies the value of the variable

2) variables do not need to participate in invariant constraints with other state variables


The second semantics of using volatile variables is to prohibit command reordering optimizations, and ordinary variables only guarantee that the correct results will be obtained in all areas that depend on the results of the execution of the method, without guaranteeing that the order of the variable assignment is consistent with the order of execution in the program Code. Because this is not perceptible during the execution of a thread's methods, this is what is described in the Java memory model as the so-called "line range that behaves as a serial semantics" (within-thread as-if-serial semantics).

Order reordering Issues.

In some cases, The performance of the volatile synchronization mechanism is really better than the lock (using the synchronized keyword or the lock inside the java.util.concurrent package), but due to the many elimination and optimization of the virtual machine lock implementation, It is difficult to quantify that volatile is more than synchronize D How much faster. If you allow volatile to compare itself with yourself, you can determine the principle that the performance consumption of volatile variable read operations is almost no different from normal variables, but the write operation may be slower because it requires inserting many memory barrier directives in the local code to ensure that the processor does not run out of Order. But even so, the total cost of volatile in most scenarios is still lower than the lock, and the only reason we choose between volatile and locked is just whether the semantics of volatile can satisfy the needs of the usage scenario.

If T represents a thread, and V and W represent two volatile variables respectively, the following rules are required for read, load, use, assign, store, and write Operations:

1) the thread t can perform the use action on the variable v only if the previous action of the thread t to the variable v is load, and the thread T can perform the load action on the variable v only if the last action that the thread T performs on the variable V is use. The use action of the thread T on the variable v can be considered to be associated with the load, read action of the variable V for the thread t, which must be present together (this rule requires that in working memory, the latest value must be flushed from main memory before each use of V to ensure that the modified value of the variable v is visible to other threads).

2) the thread t can perform a store action on the variable v only if the previous action of the thread T on the variable v is assign, and the thread T can perform the Assign action on the variable v only if the last action that the thread T performs on the variable V is store. The Assign action of the thread t to the variable v can be considered to be associated with the store, write action of the variable V for the thread t, and must be present together (this rule requires that in working memory, the memory must be synchronized back to main in a row after each change. Used to ensure that other threads can see their own modifications to the variable v).

3) assume that action A is the use or assign action of the thread T to the variable v, assuming that the action F is the load or store action associated with action a, assuming that the action P is a read or write action corresponding to the variable V of the action f; Assuming that action B is the use or assign action of the thread T to the variable w, the action G is assumed to be the load or store action associated with action b, assuming that the action Q is the read or write action corresponding to the variable W for the action G. If a precedes b, then p is preceded by Q (this rule requires that volatile-modified variables are not optimized by instruction reordering, ensuring that the code executes in the same order as the program).


Special rules for long and double types of variables

The Java memory model requires that all 8 operations of lock, unlock, read, load, assign, use, store, write are atomic, but for 64-bit data types (long and double), A relatively loose rule is defined in the Model: allows the virtual machine to divide the read and write operations of 64-bit data that are not volatile-modified into two 32-bit operations, which allows the virtual machine implementation to select a load, store, Read and write are the atomic properties of the 4 operations, which is called the non-atomic contract of long and double (not atomic treatment of double and long Variables).

If multiple threads share a long or double type of variable that is not declared volatile, and both read and modify them, then some threads may read to a non-original value, and no other thread modifies a value that is worth representing a "half variable".

Although the Java memory model allows the virtual machine not to implement the read and write of long and double variables into atomic operations, It allows the virtual machine to choose to implement these operations as atomic, and it is strongly recommended that the virtual machine be implemented as Such. In the actual development, the current commercial virtual machines under various platforms choose to treat 64-bit data read and write operations as atomic operations, so we generally do not need to write code to use the long and double variables specifically declared as Volatile.


atomicity, visibility, and Ordering

atomicity (atomicity): Atomic variable operations that are directly guaranteed by the Java memory model include read, load, assign, use, store, write, We can roughly assume that the access read and write of the basic data type is atomic (the exception is the non-atomic contract of long and double).

The Java memory model also provides lock and unlock operations to meet this requirement, although the virtual machine does not open lock and unlock operations directly to the user, But it provides a higher level bytecode directive Monitorenter and monitorexit to implicitly use these two operations, the two bytecode instruction reflected in the Java code is the synchronization block--synchronized keyword, Thus the operation between the synchronized blocks is also atomic.

Visibility (Visibility): visibility means that when a thread modifies the value of a shared variable, other threads can immediately know the Change. The Java memory model realizes visibility by synchronizing the new value back to main memory after the variable is modified, and by refreshing the variable value from the main memory before the variable is read, which relies on the main memory as the method of passing the medium, whether it is a normal variable or a volatile variable, and the difference between a normal variable and a volatile variable is The special rules for volatile ensure that the new values are immediately synchronized to main memory and flushed from the main memory immediately before each Use. thus, It can be said that volatile guarantees the visibility of variables in multi-threaded operations, while ordinary variables do not guarantee this.

In addition to volatile, synchronized and final in Java can also achieve Visibility. The visibility of a synchronization block is obtained by synchronizing this variable back to the main memory (execute store, write Operation) before performing a unlock operation on a variable, the visibility of the final keyword is that the final-modified field is initialized in the constructor once the initialization is Complete. And the constructor does not pass the "this" reference (this refers to escaping is a very dangerous thing, other threads may have access to the "initialized half" object through this reference), the value of the final field can be seen in other THREADS.

Order (ordering): All operations are ordered if viewed within this thread, and all operations are unordered if another thread is observed in one thread. The first half of the sentence refers to "line range expression as a serial semantics" (within-thread as-if-serial semantics), The latter sentence refers to the "order reordering" phenomenon and "working memory and main memory synchronization delay" Phenomenon.

The two keywords for volatile and synchronized in Java guarantee the order of operations between threads, and the volatile keyword itself contains the semantics of prohibiting command reordering, while synchronized is the " A variable that allows only one thread to lock on it at the same time "this rule determines that two synchronized blocks holding the same lock can only be entered serially.

The Synchronized keyword can be used as one of the solutions when you need these 3 features.

First occurrence (happens-before) principle

This principle is very important, it is to determine whether the data is competitive, thread security is the main basis for this principle, we can through a few rules to solve the concurrency of the two operations between the possible conflicts between all Problems.

The first occurrence is a partial-order relationship between two operations defined in the Java memory model, and if operation a precedes operation b, It is said that the effect of operation A can be observed by Operation B before Operation b, "impact" includes modifying the values of shared variables in memory, sending messages, calling methods, and so On.

Program Order rule: within a thread, in the order of the program code, the preceding operation precedes the operation that was Written. To be precise, you should control the flow order rather than the program code order, because you want to consider the structure of branching, looping, and so On.

Tube lock Rule: A unlock operation first occurs after the lock operation facing the same lock. The same lock must be emphasized here, and the "back" refers to the chronological order of the Time.

Volatile variable rules (volatile Variable rule): The write operation of a volatile variable precedes the read operation of the variable, and the "back" here also refers to the order of Time.

Thread Start rule: the start () method of the thread object takes precedence over each action of this THREAD.

Thread termination rule: All operations in a thread occur first in the termination detection of this thread, and we can end with the Thread.Join () method, thread.isalive () The return value of the device detects that the program has terminated Execution.

Thread interrupting rule: the invocation of the thread interrupt () method first occurs when the code of the interrupted thread detects that an interrupt event occurred, and can be detected by the thread.interrupted () method.

Object Finalization rule (Finalizer rule): initialization of an object (the completion of a constructor Execution) precedes the start of the Finalize () method of Ta.

Transitivity (transitivity): If operation a first occurs in Operation b, operation B First occurs in operation c, it can be concluded that operation a precedes operation C.

The Java language does not require any synchronization to ensure that the first occurrence rules can be established only above These.

An operation "time first" does not mean that the operation will be "antecedent", then if an operation "first occurrence" whether it can be deduced that the operation must be "time to occur"? unfortunately, This inference is also not tenable, a typical example is the "order reordering" as mentioned many Times.

There is not much relationship between time sequence and the principle of antecedent occurrence, so we should not be disturbed by the time order when we measure the concurrency security problem, and everything must be based on the principle of antecedent occurrence.

Java memory model

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.