An overview of the

An in-depth understanding of the Java Virtual Machine: Advanced FEATURES and Best Practices for the JVM (3rd Edition). The Java Virtual Machine is more of a specification, and there are many specific Implementations of the Java Virtual Machine. The author mentions that most of this article uses the Hotspot VIRTUAL machine as the explanation.

Part ii Automatic memory management

Chapter 2. Java Memory regions and memory overflow exceptions

Runtime data area: counters, Java virtual machine stack (local variable table, operand stack) method area (type information, runtime constant pool, etc.), Java heap, direct memory. Memory overflow may occur. Virtual machine object creation, memory layout and access location,

Chapter 3 garbage collection and memory allocation policies

Strong reference, soft reference, weak reference, virtual reference, generational collection, concurrent accessibility analysis, mark-clean, mark-copy, Mark-collation,

The third part is virtual machine execution subsystem

Chapter 6 class file structure

Class Class file structure, bytecode instruction set

Chapter 7 Virtual machine class loading mechanism

Load, link, initializer, class loader, parent delegate model

Chapter 8 Virtual machine bytecode execution engine

Runtime stack structure, method parsing dispatch, dynamically typed language support, stack based bytecode execution

The fourth part is program compilation and code optimization

Chapter 10 front-end compilation and optimization

Java file to bytecode stage: Java annotation processor, generics, unboxing, plug-in annotation processor

Chapter 11 back-end compilation and optimization

From bytecode to machine code: AOT, JIT, Android Dalvik, Android ART

Chapter 2. Java Memory regions and memory overflow exceptions

2.1 an overview of the

The C/C++ programmer is responsible for maintaining the life of every object from start to finish. For Java programmers, Java helps them manage memory automatically without having to write explicit code to free it. However, a VIRTUAL machine is not a panacea. Once memory leaks and spills occur, it is difficult to troubleshoot errors and correct problems without understanding how the VIRTUAL machine uses memory. This chapter provides a conceptual overview of the various areas of Java virtual machine memory and their potential problems.

2.2 Runtime data area

When a Java virtual machine executes Java programs, it divides the memory it manages into run-time data areas with different functions. These zones have different users and different creation and destruction times. Some regions are created and destroyed with the lifetime of the virtual machine process, while others are created and destroyed depending on the start and end of the user thread. According to the Java Virtual Machine Specification, the memory managed by the Java Virtual Machine includes the following run-time data areas.

The Java virtual machine executes programs on a stack basis. Each thread has a corresponding virtual machine stack, and the stack frame of the virtual machine stack corresponds to the Java method.

2.2.1 Program counter

Progrom Counter Register, which records the bytecode line number indicator executed by the current thread. Generally speaking, the bytecode interpreter works by changing the value of the counter to select the next bytecode instruction to be executed. It is an indicator of the program control flow, and branches, loops, jumps, exception handling, thread recovery, and so on depend on the counter. Each thread has its own counter, which does not affect each other. Counters are allocated in thread-private memory space. OOM will not appear in this area.Copy the code

2.2.2 Java Vm Stack

The Java virtual machine Stack is also thread-private and has the same life cycle as a thread. It describes the thread-memory model of Java method execution, that is, when each method is executed, a Stack Frame is created synchronously to store local variables, operand stacks, dynamic connections, etc. Method call and execution, corresponding to a stack frame on and off the stack.Copy the code

When Java source code is compiled into bytecode, the size of the local variable table and the depth of the operand stack required for a stack frame are analyzed and calculated, that is, the amount of memory required after compilation depends on the source code and the specific memory layout of the virtual machine stack. In the Java Virtual Machine Specification, a StackOverflowError is raised if the stack depth of a thread request is greater than the depth allowed by the virtual machine. OutOfMemoryError is thrown if the virtual machine stack capacity can be expanded dynamically and indefinitely. Hotspot vm stack capacity cannot be dynamically expanded, but if the thread fails to apply for stack space, it will still receive OOM.

2.2.3 Local method stack

Whereas the Java virtual machine stack executes Java methods, the Native method stack executes Native methods. The effects are similar, and there are the same anomaly problems. In the HotSpot VIRTUAL machine, the local method stack is merged with the Java virtual machine stack.Copy the code

2.2.4 Java heap

The Java heap is an area of memory shared by all threads that is created when the virtual machine is started. Used to store object instances. An array is also an object instance. The Java heap is an area of memory managed by the garbage collector. Based on the generation collection theory, the heap memory of most virtual machines can be divided into new generation, old generation, permanent generation, Eden, Survivor, etc. With the development of garbage collector technology, there are new garbage collectors that do not use generational design, so there is no such thing as generational division. ! [3.27.29 screenshots 2020-08-05 afternoon. PNG] (HTTP: / / https://cdn.nlark.com/yuque/0/2020/png/1305846/1596612457012-03fbf28c-2cf4-43be-a24f-7144ada 446a4.png#align=left&display=inline&height=155&margin=%5Bobject%20Object%5D&name=%E6%88%AA%E5%B1%8F2020-08-05%E4%B8%8B%E 8 d % 5% 883.27.29. Png&originHeight = 166 & originWidth = 729 & size = 15139 & status = done&style = none&width = 680)Copy the code

The Java heap can be partitioned into Thread Local Allocation Buffers (TLabs) that are private to multiple threads. We say TLAB is proprietary off the shelf, but only allocation is proprietary, and actions such as read operations and garbage collection are shared by threads. Tlabs are usually allocated in Eden area. Because Eden area itself is small and TLAB actual memory is also very small, accounting for 1% of Eden space by default, it is inevitable that some large objects cannot be directly allocated in TLAB.No matter how you divide it up, it doesn’t change the way the heap holds object instances. Various partitions are used to better allocate and recycle memory. The Java Virtual Machine Specification states that memory space that is logically contiguous can be physically discontiguous. However, most virtual machine implementations require continuous physical memory space for simplicity of implementation and storage efficiency. The heap memory space of the mainstream Java virtual machine is expandable, but has a upper limit. When the object instance cannot be allocated memory and the heap reaches its limit. The Java VIRTUAL machine throws an OutOfMemoryError.

2.2.5/2.2.6 Method area (including runtime constant pool)

Method Area, Runtime Constant PoolCopy the code

Method areas, thread sharing, storage of type information that has been loaded by the virtual machine, constants, static variables, just-in-time compiler compiled code cache, and so on. JDK8, instead of using the Permanent Generation Space implementation method area, implements Metaspace in local memory. String constants are moved to the Java heap. This part of the memory reclamation target is mainly for constant pool reclamation and type offloading.Run-time Constant Pool, which holds the Constant Pool Table, the various literal and symbolic references generated at compile time by the Class file. According to the Java Virtual Machine Specification, an OutOfMemoryError is thrown if the method area cannot meet the new memory allocation.

2.2.7 Direct Memory

NIO(New input/ Output) is a New class in JDK1.4, which introduces a channel and buffer BASED I/O mode. It can use Native functions to allocate out-of-heap Memory directly. This chunk of memory is then referenced and manipulated via DirectByteBuffer objects on the heap. The size of direct memory is not limited by the JVM, but OutOfMemoryError exceptions are equally possible.

2.3 HotSpot VIRTUAL Machine Object Exploration

Take HotSpot as an example to describe the creation, structure, and access of objects in the Java heap.

2.3.1 Object Creation

Allocate heap memory in two ways: Bump The Pointer and Free List, depending on whether The memory is tidy. The former memory is neat.Two ways to address thread safety issues in concurrent situations

  • The operation of memory allocation is synchronized. In fact, CompareAndSwap (CAS) is configured to ensure atomicity
  • Thread Local Allocation Buffer (TLAB) : a private write memory area that is pre-allocated by a Thread.

2.3.2 Object Structure

  • Object Header
    • Object’s own runtime data: hash code, GC generation age, lock status flag, thread holding lock, bias thread ID, bias timestamp, etc. This part of data is called Mark Word in 32-bit and 64-bit sizes on 32-bit and 64-bit VMS.
    • Type pointer to its type metadata.
  • Instance Data
    • Object actually stores valid information
  • Align Padding
    • Ensure that the object memory size is an integer multiple of 8 bytes, and align the fill completion.

2.3.3 Locating objects

According to the Java Virtual Machine Specification, the reference type data on the stack is just a reference to an object. There are actually two ways to access objects by reference:

  • Handle access, reference stores the address of the handle, the object is moved, change the pointer in the handle, the reference itself will not be modified.
  • Direct pointer access reduces the overhead of a pointer location, and object access is very frequent in Java. HotSpot uses direct pointer access.

2.4 Field: OutOfMemoryError

Simulates Java heap, virtual machine stack, local method stack, method area, runtime constant pool, local direct memory overflow.

2.4.1 Java heap Overflow

/***Intellij IDEA Run Configgurations * VM options: - Xms20m - Xmx20m - XX: + HeapDumpOnOutOfMemoryError * limit the size of the Java heap to 20 MB, extendable (the heap of minimum - Xms parameters and maximum -xmx parameter is set to the same) * / 
public class HeapOOM {
    static class OOMObject{}
    public static void main(String[] args) {
        List<OOMObject> list=new ArrayList<OOMObject>();
        while (true){
            list.add(newOOMObject()); }}}Copy the code

The results

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid58651.hprof ...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Heap dump file created [27770272 bytes in 0.159 secs]
Copy the code

2.4.2 Vm stack and local method stack Overflow

HotSpot does not distinguish between virtual machine stacks and local method stacks and requires -xss. -Xoss(setting local method stack) has no effect.Copy the code

Two kinds of exceptions:

  • A StackOverflowError occurs when the stack depth is greater than the maximum allowed stack depth.
  • Stack memory can be dynamically expanded, when insufficient memory cannot apply, OutOfMemoryError.

HotSpot stack memory does not allow dynamic expansion, so we use the -xss argument to reduce stack memory.

// VM Args: -xss160k
public class JavaVMStackOF {
    private int stackLength = 1;
    
    public void stackLeak(a) {
        stackLength++;
        System.out.println("stack length:" + stackLength);
        stackLeak();
    }
    public static void main(String[] args) throws Exception {
        JavaVMStackOF oom = new JavaVMStackOF();
        try {
            oom.stackLeak();
        } catch (Exception e) {
            e.printStackTrace();
            throwe; }}}Copy the code

The results

. stack length:754
at oom.JavaVMStackOF.stackLeak(JavaVMStackOF.java:9)
at oom.JavaVMStackOF.stackLeak(JavaVMStackOF.java:10)
at oom.JavaVMStackOF.stackLeak(JavaVMStackOF.java:10)...Copy the code

2.4.3 Method area and runtime constant pool overflow

Run String:: Intern () in JDK6 and earlier

In JDK6 and previous hotspots, constant pools are allocated in permanent generations, limiting the size of permanent generations by -xx :PermSize and -xx :MaxPermSize. String:: Intern () can add a String object to the constant pool if the String object is not included in the constant pool. In HotSpot in JDK6

*** VM Args: -xx :PermSize=6M -xx :MaxPermSize=6M */ 
public class RuntimeConstantPoolOOM { 
    public static void main(String[] args) {
        // Use Set to keep constant pool references to avoid Full GC reclaiming constant pool behavior
        Set<String> set = new HashSet<String>(); 
        // Within the short range, 6MB of PermSize is enough to generate OOM
        short i = 0; 
        while (true) { set.add(String.valueOf(i++).intern()); }}}Copy the code

The result also verifies that string constants are in the permanent generation

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space 
at java.lang.String.intern(Native Method) 
at org.fenixsoft.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java: 18
Copy the code

Running String:: Intern () in JDK7 and later

Because string constants are moved to the Java heap, setting -xx :MaxPermSize in JDK7 or –XX: maxmeta-spacesize in JDK8 does not cause overflow problems in JDK6. But we can limit the maximum heap memory space -Xmx6m, resulting in OOM.

/*** VM Args:-Xmx6m
 */
public class RuntimeConstantPoolOOM {
    public static void main(String[] args) {
        Set<String> set = new HashSet<String>();
        short i = 0;
        while (true) { set.add(String.valueOf(i++).intern()); }}}Copy the code

The result also verifies that string constants are in the heap

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.HashMap.put(HashMap.java:611)
	at java.util.HashSet.add(HashSet.java:219)
	at oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:27)
Copy the code

2.4.4 Local direct Memory Overflow

The DirectMemory capacity can be specified by using -xx :MaxDirectMemorySize.

/*** VM Args: -xmx20m-xx :MaxDirectMemorySize=10M */
public class DirectMemoryOOM {

    public static void main(String[] args) throws Exception {
        int count=1;
        Field unsafeFiled=Unsafe.class.getDeclaredFields()[0];
        unsafeFiled.setAccessible(true);
        Unsafe unsafe= (Unsafe) unsafeFiled.get(null);
        while (true){
            unsafe.allocateMemory(1024*1024*1024); System.out.println(count++); }}}Copy the code

The results

Exception in thread "main" java.lang.OutOfMemoryError
	at sun.misc.Unsafe.allocateMemory(Native Method)
	at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:17)
Copy the code

2.5 summary

So far, you’ve seen how memory is divided in a virtual machine, and how it can overflow. The next chapter details how the Java garbage collection mechanism avoids memory overruns.

Chapter 3 garbage collection and memory allocation policies

3.1 an overview of the

Garbage Collection (GC), a 1960 MIT Lisp language that uses dynamic memory allocation and Garbage Collection techniques. When Lisp was an embryo, its author John McCarthy thought about three things GC needed to accomplish:

  • Which memory needs to be reclaimed
  • When to recycle
  • How to recycle

In the Java virtual machine memory runtime, program counters, virtual machine stacks, and local method stacks are created and killed with the thread. In the compiler its size is basically determined. GC focuses on thread-shared areas of the Java heap and method areas.

3.2 Is the Object Dead?

How do you determine which objects need to be reclaimed, or are dead, before GC can reclaim the heap?

3.2.1 Reference Counting Method

3.2.2 Reachability Analysis

Objects that can be fixed as GC Roots include:

  • The object referenced by the local variable table in the vm stack frame
  • Class static properties in the method area reference objects
  • A constant reference object in a method area
  • JNI reference objects in the local method stack
  • Internal references to virtual machines, such as Class objects, resident nullPointExceptions, Class loaders, and so on
  • The object held by the synchronization lock
  • Jmxbeans that reflect Java virtual machine internals, callbacks registered in JVMTI, native code caches, and so on.

In addition to fixed, there are temporary other objects.

3.2.3 References again

Java classifies references into four types:

  • Strongly Reference, Reference assignment (Object obj=new Object), will never be recycled
  • Soft reference, which survives until a memory overflow exception occurs
  • Weak references survive until the next garbage collection
  • Phantom Reference: you can’t get an object instance from a Phantom Reference, but only sense that the object is recycled

3.2.4 To be or not to be

To declare an object dead, there must be at least two non-compliance procedures. To override the Finalize () method of an object, we can execute finalize() after the first tag to reattach the reference chain to avoid collection, but Finalize () will only be executed once.

3.2.5 Recycling method area

3.3 Garbage collection algorithm

  • Reference Counting GARBAGE Collection (GC)
  • Tracing GC

Here we are talking about tracking garbage collection.

3.3.1 Generation collection theory

The theoretical hypothesis basis of Generational Collection:

  • Weak Generational Hypothesis

The vast majority of objects are ephemeral

  • The Strong Generational Hypothesis:

The more garbage collections an object goes through, the harder it is to die

  • The Intergenerational Reference Hypothesis:

Cross-generation references are very rare compared to same-generation references

Based on the weak/strong generational hypothesis, the average Java virtual machine will at least divide the Java heap into

  • Young Generation
  • The Old Generation

With each garbage collection, a large number of dead objects from the new generation are recycled, and the surviving objects are gradually promoted to the old age. Big object goes straight to the old age. Create a global data structure (Remebered Set) for the new generation, breaking up the old into smaller chunks and identifying cross-generational references for chunks. For different levels of generational collection, we define the terms:

  • Partial GC
    • New Generation Collection (Minor GC/Young GC)
    • Major GC/Old GC
    • Mixed GC, collecting Cenozoic and part of old age
  • Full Heap Collection (Full GC)

3.3.2 Mark-clear algorithm

Mark-swap 1960 Lisp John McCarthy, basic algorithm. Disadvantages:

  • Execution efficiency is unstable and decreases as the number of objects increases
  • Memory fragmentation problem

3.3.3 Mark-copy algorithm

1969 Fenichel “Half Region Copying” SemisSpace solved the problem of memory fragmentation and execution efficiency, with the obvious disadvantage of wasting half of memory.

IBM research found that 98% of the new generation objects did not survive the first round of collection, so 1:1 memory allocation was not used. In 1989, Andrew Appel proposed a more optimized half-region replication generation strategy. As you can see, there is always a Survivor(10% of the new generation memory) reserved to hold the collected surviving objects. If there is not enough Survivor space, Handle Promotion is required for the older generation.The mark-copy algorithm is suitable for the new generation, where a large number of objects are reclaimed and few objects need to be copied. Old age subjects have a high survival rate, so it doesn’t apply.

3.3.4 Mark-collation algorithm

Mark-compact 1974 Edward Lueders. Mobile recovery algorithm, mark-sweep is non-mobile. When moving an object, it is complicated to reclaim it, and when not moving an object, it is complicated to allocate memory.

3.4 Details of HotSpot garbage collection algorithm

3.4.1 Enumeration of root Nodes

All garbage collection algorithms need to pause The user thread while The root node is enumerating, i.e. Stop The World! HotSpot uses Exact garbage collection, using a data structure called OopMap to record references from local variables on the stack to objects on the heap. Thus reducing the amount of time spent on root node enumeration. Finding Pointers/references on the stack introduces conservative, semi-automatic, accurate garbage collection, and also leads to OopMap.

3.4.2 safer

The root node enumeration needs to suspend the thread. It cannot interrupt the thread after every instruction, so there are fixed instruction locations called safe points for interruption. Use active interrupts, where a safe point is reached and the interrupt thread is checked for execution. The position of the safe point is generally as follows:

  • End of loop
  • Method returns before/after calling the method’s call instruction
  • Where an exception may be thrown

3.4.3 Security Zone

Safe region. A safe zone is a code fragment in which reference relationships do not change and where it is safe to begin garbage collection.

3.4.4 Memory set and card table

RememberSet, keep a record of non-collector to collector references to avoid adding the entire non-collector to the GC Root scan. For example, when collecting new generation objects, avoid GCRoot scanning for the entire old generation.Copy the code

In terms of accuracy, memory sets can be divided into

  • Word length accuracy
  • Precision of object
  • Card precision, each record is accurate to a memory region, record whether the region contains cross-generation Pointers.

Card table is the memory set of common card accuracy. Simply put, a Card Table can be just a byte array. Each element of the array corresponds to a Card Page, which is a block of memory of a specific size, typically two to the NTH power of bytes, or 512 bytes in HotSpot. If an object in a card page has a cross-generation reference, the corresponding card table element is marked as 1, i.e. the element is Dirty.

3.4.5 Write barriers and AOP

When to record a RememberSet? Write Barrier, the VIRTUAL machine level AOP aspect of the “reference type field assignment” action, the virtual machine generates instructions for the assignment operation. Circular notifications, providing pre-write and post-write barriers.

Assume that the processor’s cache row size is 64 bytes, and since one card table element is 1 byte, all 64 card table elements will share the same cache row. False Sharing problem of card table in high concurrency. Check whether the card table is dirty before writing dirty. After JDK 7, the HotSpot virtual machine added a new parameter -xx: +UseCondCardMark that determines whether to enable conditional judgment for card table updates

3.4.6 Concurrency accessibility analysis and tricolor marking

We’ve sped up root enumerations with OopMap, security zones, RememberSet, and more. The pauses caused by root enumerations are already fairly short and fixed, whereas the pauses of objects traversed down from GC Roots are proportional to the heap capacity. Reachability analysis (marking) algorithm requires the whole process to be analyzed in a consistent snapshot, which is bound to freeze all user threads, and the freezing time is completely uncontrollable. Freezing time is unacceptable in the case of large heap capacity. Therefore, it is best if the reachability analysis process can be executed concurrently with the user thread. Let’s start with the concurrency reachability analysis process known as tricolor taggingConcurrency reachability analysis raises two further types of problems:

  • 1. This Garbage is not marked (Floating Garbage). This is acceptable.
  • 2. Objects that should not be collected are marked (objects disappear), which is unacceptable because the user thread needs objects that are gone.

The object disappearance problem occurs if and only if both of the following conditions are met, i.e. objects that should be black are mislabeled as white (Wilson, 1994) :

  • The assigner inserts a black to white reference.
  • At the same time, the assignment removes all direct or indirect references from gray to that white.

  • Incremental update: records the newly added references. After the concurrent scan is complete, the black is scanned again, that is, the black becomes gray
  • The original snapshot records deleted references. After the concurrent scan is complete, the scan is performed in gray again.

3.5 Classic garbage collector

3.5.0 overview

Before introducing us, let’s clarify a few concepts:

  • Parallel, where multiple garbage collection threads can run at the same time. Serial can only have one garbage collection thread running at a time.
  • Concurrent, garbage collection threads can run at the same time as user threads.
  • High throughput, garbage collection time /(user thread running time + garbage collection time)
  • Low latency, fast response. The total collection time can be increased and the average collection time can be reduced.

3.5.1 Serial Collector

New generation, no parallelism, no concurrency, tag replication, simple and efficient, minimum Memory Footprint, suitable for single-core/low-core, prior to JDK1.3.1.

3.5.2 ParNew collector

New generation, parallel, multithreaded version of Serial, tag replication, [jdk1.3-jdk8]

3.5.3 Parallel Scavenge

New generation, parallel, no concurrency, JDK1.4, mark replication, focus on throughput

3.5.4 Serial Old Collector

Serial Serial Serial Serial Serial Serial Serial Serial Serial Serial Serial

3.5.5 Parallel Old Collector

Avenge, Parallel, non-concurrent, Parallel Scavenge, insane, throughput oriented

3.5.6 CMS Collector

Concurent Mark Sweep, old age, parallel, concurrent, [JDK5-JDK8], Mark Sweep, focus on low latency, concurrent Mark uses incremental updates. Four steps:

    1. CMS Initial mark, which marks only GC Roots directly associated objects, short STW
  • 2) CMS Concurrent mark, traversing the entire object graph, time-consuming, can be concurrent with the user thread
  • 3) RE-mark (CMS remark), incremental update, avoid object disappearance problem, short STW
  • 4) CMS Concurrent sweep, which does not require moving objects, can be concurrent with user threads

Three obvious disadvantages of CMS:

  • The number of reclaim threads started by CMS by default (number of processor cores +3)/4. In the concurrent phase, the application slows down and throughput decreases
  • Floating Garbage, concurrent process failed, need to enable Serial Old, do an Old collection.
  • Memory fragmentation, a problem caused by the token-clearing algorithm, which performs a defragmentation after several token-clearing operations. Because collation requires moving objects, it cannot be concurrent.

3.5.7 G1 collector

Garbage First, JDK7 improved, and JDK9 became the default Garbage collector. New generation, old age. Region partitioning, local mark-copy, whole mark-collation, focus on low latency, and concurrent markups using raw snapshots. Large objects (with more than half of Region memory) directly enter the Humongous Region. Region is the smallest unit of collection. Each Region can play the role of Eden space, Survivor space, or old age space as required. A predictable temporal pause model. Four steps:

    1. Initial Marking, Marking only GC Roots directly associated objects, short STW
  • 2) Concurrent Marking, traversing the whole object graph, time-consuming, can be Concurrent with the user thread
  • 3) Final Marking, concurrent Marking using original snapshot, to avoid object disappearance problem, short STW
  • 4) Live Data Counting and Evacuation (Live Data Counting and Evacuation), which sorts the value and cost of each Region’s recovery and combines any multiple regions’ recovery based on the expected pause time. The Region alive object to be reclaimed is copied to an empty Region. Reclaiming an old Region involves object movement, and STW is required

G1 is a milestone in the history of garbage collector technology, pioneering the idea of locally-oriented cell phone design and region-based memory layout. Since G1, the design orientation of the garbage collector has changed to pursue Allocation rates rather than cleaning up the entire Java heap at once.More on the G1

3.6 Low-latency garbage collector

3.6.1 Shenandoah collector

Not really

3.6.2 ZGC collector

Not really

Chapter 6 class file structure

The result of compiling code from native machine code to bytecode is a small step in the development of storage formats, but a giant leap in the development of programming languages.

6.1 an overview of the

Program language –> bytecode –> binary local machine code

6.2 The cornerstone of irrelevance

The cornerstone of irrelevance — Byte Code platform – independence, language – independence

6.3 Class File Structure

A Class file is an 8-byte binary stream in which data items are arranged in a tight sequence without any delimiters. There are only two types of data in the Class file structure:

  • Unsigned number, basic data type. U1, U2, U4, u8 represent unsigned numbers of 1, 2, 4, and 8 bytes respectively, and are used to describe numbers, references, quantity values, or UTF-8 encoded strings.
  • Table consisting of multiple unsigned numbers conforming to the data type. The name usually ends with _info.
type The name of the The number of explain
u4 magic 1 4 bytes magic number 0xCAFEBABE
u2 minor_version 1 Minor version number
u2 major_version 1 Major Version number
u2 constant_pool_count 1 Constant pool counts values
cp_info constant_pool constant_pool_count-1 Constant pool
u2 access_flags 1 Access tokens
u2 this_class 1 Class index
u2 super_class 1 Index of the parent class
u2 interfaces_count 1
u2 interfaces interfaces_count Interface index set
u2 fields_count 1
field_info fields fields_count Collection of field tables, class variables
u2 methods_count 1
method_info methods methods_count Method table collection
u2 attributes_count 1
attribute_info attributes attributes_count Property sheet collection

Let’s start with testClass.java

package clazz;
public class TestClass {
    public static void main(String[] args) {}private int m;
    public int inc(a){return m+1;}
}
Copy the code

The binary bytecode file testClass.class is compiled

Javap -v testclass. class gets the class information contained in the bytecode. All we need to do now is simulate the javap parsing process.

Last modified 2020-7-29; size 483 bytes
  MD5 checksum ad62060802ee27c385e20042d24e8b38
  Compiled from "TestClass.java"
public class clazz.TestClass
  minor version: 0
  major version51:flags: ACC_PUBLIC.ACC_SUPER
Constant pool# 1:= Methodref          #4.#22         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#23         // clazz/TestClass.m:I
   #3 = Class              #24            // clazz/TestClass
   #4 = Class              #25            // java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lclazz/TestClass;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               inc
  #19 = Utf8               ()I
  #20 = Utf8               SourceFile
  #21 = Utf8               TestClass.java
  #22 = NameAndType        #7: #8          // "<init>":()V
  #23 = NameAndType        #5: #6          // m:I
  #24 = Utf8               clazz/TestClass
  #25 = Utf8               java/lang/Object
{
  public clazz.TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lclazz/TestClass;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  args   [Ljava/lang/String;

  public int inc(a);
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lclazz/TestClass;
}
SourceFile: "TestClass.java"
Copy the code

6.3.1 Magic numbers and Class file versions

Magic Number CAFEBABE: this is a Class file, 4 bytes. Minor version: 0 (0x0000), 2 bytes. Major Version: 51 (0x0033), 2 bytes.

6.3.2 constant pool

Next to magic numbers and versions is the constant pool. The constant pool holds two types of constants:

  • Literals: text strings, constants declared final, etc
  • Symbolic References
    • package
    • Fully qualified names of classes and interfaces,
      • # 13, Lclazz/TestClass
    • The name and descriptor of the field,
      • #5 = Utf8               m
      • #6 = Utf8               I
    • The name and descriptor of the method,
      • #14 = Utf8 main,
      • #15 = Utf8             ([Ljava/lang/String;)V
    • Method handles and method types,
      • #23 = NameAndType        #5:#6          // m:I
    • Dynamic call points and dynamic constants

There are 17 types of constants in the constant pool, such as CONSTANT_Methodref_info,CONSTANT_Classref_info,CONSTANT_Utf8_info, etc. The structure of each type of constant is also different. The common denominator is that they all start with a tag of U1, indicating the type. The complete definitions are listed in Understanding the Java Virtual Machine. Here are a few examples.

CONSTANT_Methodref_info:

  • Tag u1 has a value of 10
  • Index, u2 Points to the index of the class descriptor CONSTANT_Classref_info that declares the method
  • Index, u2 points to the index of the name and type descriptor CONSTANT_NameAndType_info

CONSTANT_Classref_info:

  • Tag u1 has a value of 7
  • Index, u2 Points to the index of the fully qualified named constant item

Number of constant pool items: 25 (0x001A is 26, the constant pool index starts at 1, 0 is reserved, so actually only 25 constants are used, 0 can be interpreted as not referencing items in the constant pool).

Methodref (0A 00 04 00 16), #1 = Methodref

  • 0A, tag, u1 10 means CONSTANT_Methodref_info
  • 00 04, index, u2, index of the Class descriptor, that is, #4 = Class #25 // Java /lang/Object
  • 00 16, index, u2, NameAndType descriptor index, i.e. #22 = NameAndType #7:#8 // “”:()V

Note that the parent method is of type Methodref and the inc method is of type UTf8.

M :I (09 00 03 00 17), #2 = Fieldref #3

  • 09, tag, u1 9 indicates CONSTANT_Fieldref_info
  • 00 03, index, u2 is the index of the Class description, #3 = Class #24 // clazz/TestClass
  • 00 17, index, u2 is the 23 NameAndType descriptor index, that is, #23 = NameAndType #5:#6 // m:I

The remaining 23 items, too many, if you get the idea.

6.3.3 Access flags

The constant pool 25 item parsing is completed, followed by the Class access flag (access_flags) 00 21 representing 0x0020&0x0001

  • 0x0001 ACC_PUBLIC Specifies whether the ACC_PUBLIC type is public
  • 0x0020 ACC_SUPER Is allowed to use the new semantics of the Invokespecial bytecode instruction. The Invokespecial semantics were changed in JDK1.0.2. For classes compiled after 1.0.2, this flag must be true.

6.3.4 Set of Class Indexes, parent Indexes, and interface Indexes

  • Class index,u2,0x0003, #3 = Class #24 // clazz/TestClass
  • Parent index,u2,0x0004, #4 = Class #25 // Java /lang/Object
  • Number of interface indexes,u2,0x0000, no interface

6.3.5 Collection of field tables

  • Fields_count,u2,0x0001, indicates a field
  • Access_flags, u2, 0 x0002 ACC_PRIVATE
  • Name_index,u2,0x0005, #5 = Utf8 m
  • Descriptor_index,u2,0x0006, #6 = Utf8 I
  • Attributes_count, u2, 0 x0000, no
  • Attributes_info, no

6.3.6 Collection of method tables

Number of methods,u2,0x0003, there are 3 methods method table structures

  • Access_flags, u2, 0 x0001
  • Name_index, u2, 0 x0007
  • Descriptor_index, u2, 0 x0008
  • Attributes_count, u2, 0 x0001
  • attribute_info
    • Attribute_name_index,u2,0x0009, corresponds to the Code attribute, which is then resolved by Code
    • X0000002f attribute_length u4, 0
    • Max_stack, U2,0x0001, maximum depth of operand stack
    • Max_locals, U2,0x0001, the space required by the local variable table
    • Code_length, u4, 0 x00000005, code length is 5 u1
    • Code, u1, 2 a, B7, 00,01, B1
      • 2A, corresponding to aload_0, pushes local variables of type reference in slot 0 to the operand stack
      • B7, corresponding to the instruction invokespecial,
      • Zero zero, for noP, do nothing
      • 01, corresponding to aconst_NULL, pushes null to the top of the stack
      • B1, corresponding to the instruction return
    • Exception_table_length, u2, 0 x0000
    • exception_table,
    • Attributes_count, u2, 0 x0002
    • The attributes… Behind will not parse, will be good.

6.3.7 Property Table Set

In the method table, we encounter a property “Code”, along with other properties.

The attribute name Use location meaning
Code Method table Bytecode instructions compiled into Java code
ConstantValue Field in the table Constant value defined by the final keyword
LocalVariableTable Code attributes Method local variable description
SourceFile The class file Record the class file name
. . .

I’m not going to do the parsing.

6.4 Introduction to bytecode Instructions

Java virtual machine instruction, single-byte OpCode + Operand (zero or more). For operand stacks, not registers. Most instructions do not contain operands, and their arguments are placed in the operand stack. Instruction execution process simple pseudo-code

do{automatically calculate PC register value plus1; According to the PC register indicated position, from the bytecode stream opcodes;ifRetrieves operands from the bytecode stream; Perform the operation defined by the opcode; }while(Bytecode stream length >0);
Copy the code

6.4.1 Bytecode and data types

Most instructions contain the data types required by their operations, such as ILOAD, which loads int data from a local table of variables into the operand stack. I for int, L for long, f for float, and A for reference. There are also instructions that are independent of the data type, such as goto.

6.4.2 Instruction Classification

  • Load and store instructions, iload iload_0, iload_1, fload, istore, bipush sipush, wide…
  • Operation instruction, IADD, ISub, IDIV, ISHL, IOR, IXOR, IInc, DCMPG
  • Type conversion instruction, narrow explicit type conversion I2B, I2C, D2F.
  • Object creation and access directives, New, Newarray, Anewarray, getField, Baload, iaload
  • Operand stack management instructions, pop, POP2, dup2_x1,swap
  • Control transfer commands, iFEQ, tableswitch, GOto, GOTO_W
  • Method call and return instructions, Invokevirtual, InvokeInterface, Invokespecial, Invokestatic, InvokeDynamic
  • Exception handling instruction, athrow
  • Synchronization instruction.

And so on. This chapter covers bytecode composition and instructions.

Chapter 7 Virtual machine class loading mechanism

7.1 an overview of the

Class loading mechanism: The process from a Class file to an in-memory Java type. There can be overlaps between phases. Class loading is performed at run time and is also described as dynamic loading and dynamic wiring.

7.2 Class loading time

The Java Virtual Machine Specification does not enforce constraints on when to start loading. There are, however, only six cases in which a class must be “initialized” immediately if it is not initialized (loading, linking must take place first), called an active reference:

  • New, getstatic, putstatic, invokestatic bytecode instructions are encountered
    • The new directive instantiates the object.
    • The getstatic/ putStatic directive accesses its static objects (except those modified by final, which have been put into the constant pool at compile time).
    • The instruction invokestatic invokes its static methods.
  • Reflection calls
  • When a subclass is initialized, initialization of its parent class is triggered first
  • When the virtual machine starts, initializes the main class specified by the user to execute
  • The result of MethodHandle is REF_getStatic,REF_setStatic,REF_invokeStaitc,Ref_newInvokeSpecial
  • When an interface defines a new default method in JDK 8 (default modification)

Examples of scenarios that do not trigger class initialization:

  • Referring to a static field of a parent class by subclass does not cause subclass initialization
  • Defining a reference class through an array does not trigger initialization of the class
  • References constants that have been put into the constant pool at compile time.
// Scenario 1 using a subclass to refer to a static field of the parent class does not result in subclass initialization
public class SuperClass {
    static { System.out.println("SuperClass init!"); }public static int value=123;
}

public class SubClass  extends SuperClass{
    static { System.out.println("SubClass init!");}
}

public class NoInitialization1 {
    public static void main(String[] args) { System.out.println(SubClass.value); }}Copy the code
// Scenario 2 defines a reference class through an array, which does not trigger initialization
public class NoInitialization2 {
    public static void main(String[] args) {
        SuperClass[] scarray=new SuperClass[10]; }}Copy the code
// Scenario three references constants that have been put into the constant pool at compile time.
public class ConstClass {
    static {
        System.out.println("ConstClass init!");
    }
    public static final String HELLOWRLD="hello world";
}
public class NoInitialization3 {
    public static void main(String[] args) { System.out.println(ConstClass.HELLOWRLD); }}Copy the code

For scenario 3 we look at the bytecode of NoInitialization3 and see that “Hello World” is already in its constant pool, pushing constants using the LDC instruction. System.out uses the getstatic directive. This does not involve initialization of a ConstClass

Constant pool:
  #4 = String             #25            // hello world
  #25 = Utf8               hello world
{
  public static void main(java.lang.String[]);
    Code:
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #4                  // String hello world
         5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;) V
         8: return
}
Copy the code

7.3 Class Loading Process

7.3.1 load

Loading: From a static file to a runtime method area. Do three things:

  • Gets the binary byte stream that defines a class by its fully qualified name.
    • Can be read from ZIP packages (JAR,WAR, etc.)
    • From the network, such as a Web Applet
    • Runtime computing generation, such as dynamic Proxy technology, the “*$Proxy” Proxy class
    • Read from database
    • Read in encrypted file
    • .
  • Translate the statically like storage structure of this byte stream into the runtime data structure of the method area.
  • Generate a java.lang.Class object in memory that represents this Class and acts as an access point for the Class’s various data in the method area.

Use the Java virtual machine’s built-in boot class loader or a custom class loader.

7.3.2 validation

Ensure byte streams are compliant with the Java Virtual Machine Specification and code security issues are verified. Validation is important, but not necessary. Four stages:

  • File format validation. After this stage is passed, it is stored in the method area. The later stages validate based on the method area data without reading the byte stream.
  • Metadata verification, class metadata information semantic verification
  • Bytecode validation, the most complex, examines the Code portion of a class. Program semantic legitimacy, security and so on
  • Symbolic reference validation, occurred during the parsing process, if not through reference symbol verification, Java virtual opportunity thrown Java. Lang. IncompatibleClassChangeError subclass exception, such as NoSuchFieldError, NoSuchMethodError and so on.

7.3.3 preparation

Typically, for class variables (static variables), memory is allocated and an initial value (zero) is set. The initial value is not 123 as assigned in the code. 123 is going to wait until initialization.

public static int value = 123;
Copy the code

Compile to a class file

public static int value;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC
...
 static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
         0: bipush        123
         2: putstatic     #2                  // Field value:I
         5: return.Copy the code

In some cases, set the initial value to 456. Such as final modified variables. Because the variable 456 is added to the constant pool ahead of time.

public static final int value2 = 456;
Copy the code
public static final int value2;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 456
Copy the code

Sections 7.3.4 parsing

The process of replacing symbolic references in a constant pool with direct references. For example, we want to replace #2 with the actual class reference, which would be involved in the class loading process if it was not loaded.

getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
Copy the code
  • Class or interface resolution
  • Field analytical
  • Method resolution
  • Interface method parsing

7.3.5 initialization

Executes class constructor () methods, non-instance constructor () methods. () method: Execute class variable assignment statement and static statement block (static{}). The order is determined by its order in the source file. Example 1: Reference variables forward illegally. Static {}; static{};

public class PrepareClass {
    static {
        value=3;
        System.out.println(value);// value: illegal forword reference
    }
    public static int value=123;
}
Copy the code

But here it is

public class PrepareClass {
    public static int value=123;
    static {
        value=3;
        System.out.println(value);// value: illegal forword reference}}Copy the code

Class file Reference

0: bipush        123
2: putstatic     #2                  // Field value:I
5: iconst_3
6: putstatic     #2                  // Field value:I
9: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
12: getstatic     #2                  // Field value:I
15: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
18: return
Copy the code

Example 2: () Execution sequence. When a subclass is initialized, its parent class is initialized first

public class TestCInitClass2 {

    static class Parent {
        public static int A = 1;
        static {
            A = 2; }}static class Sub extends Parent {
        public static int B = A;
    }

    public static void main(String[] args) { System.out.println(Sub.B); }}Copy the code

Output:

2
Copy the code

The Java virtual machine must ensure that the () method is synchronized in a multithreaded environment.

7.4 Class loader

The code that implements “getting the binary stream of a Class by its fully qualified name” is called a Class Loader.

7.4.1 Classes and classloaders

Class and its loader determine the uniqueness of the class within the Java virtual machine.

The majority of Java programs use the following three system-provided classloaders for loading:

  • BootStrap Class Loader
  • Extension Class Loader
  • Application Class Loader

In addition to the above three, there are user-defined loaders, which are implemented by integrating java.lang.ClassLoader classes.

Start the class loader

Load Java core library, native code implementation, does not inherit java.lang.ClassLoader

URL[]  urls= sun.misc.Launcher.getBootstrapClassPath().getURLs();
for(URL url : urls) { System.out.println(url); } Result output: file:.. /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/resources.jar file:.. /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/rt.jar file:.. /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/sunrsasign.jar file:.. /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/jsse.jar file:.. /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/jce.jar file:.. /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/charsets.jar file:.. /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/jfr.jar file:.. /jdk18.. 0 _73.jdk/Contents/Home/jre/classes
Copy the code

Extend the class loader

Load the Java extension library, load the Java class in the Ext directory

 URL[]  urls= ((URLClassLoader) ClassLoader.getSystemClassLoader().getParent()).getURLs();
for(URL url : urls) { System.out.println(url); } Output: file:/... /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/ext/sunec.jar file:/... /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/ext/nashorn.jar file:/... /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/ext/cldrdata.jar file:/... /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/ext/jfxrt.jar file:/... /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/ext/dnsns.jar file:/... /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/ext/localedata.jar file:/... /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar file:/... /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar
Copy the code

Application class loader

Load the Java application’s classes. To get through this. GetSystemClassLoader ().

URL[]  urls= ((URLClassLoader) ClassLoader.getSystemClassLoader().getParent()).getURLs();
for(URL url : urls) { System.out.println(url); } Output: file:/... /jdk18.. 0 _73.jdk/Contents/Home/jre/lib/ext/sunec.jar ... file:/... /jdk18.. 0 _73.jdk/Contents/Home/lib/tools.jar file:/... /java_sample/out/production/java_sample/// This is our application
file:/Applications/IntelliJ%20IDEA.app/Contents/lib/idea_rt.jar
Copy the code

Custom class loaders

7.4.2 Parental delegation model

ClassLoader.loadClass

protectedClass<? > loadClass(String name,boolean resolve)throws ClassNotFoundException{
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loadedClass<? > c = findLoadedClass(name);if (c == null) {
                try {
                    if(parent ! =null) {
                        c = parent.loadClass(name, false);
                    } else{ c = findBootstrapClassOrNull(name); }}catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.c = findClass(name); }}if (resolve) {
                resolveClass(c);
            }
            returnc; }}Copy the code

AppClassLoader, ExtClassLoader URLClassLoader inheritance. URLClassLoader.findClass(name)

protectedClass<? > findClass(final String name)throws ClassNotFoundException {
       // 1
    // 2, read the class file from disk into memory according to absolute path
    byte[] raw = getBytes(name); 
    // convert binary data to class objects
    return defineClass(raw);
    }
Copy the code

If we were to implement a ClassLoader ourselves, we would basically inherit the ClassLoader and override the findClass method with defineClass at the end of the method. ** Parent delegates ensure global uniqueness of classes. For example, any class loader that needs to load java.lang.Object delegates it to the top bootstrap class loader.

Start class loaders, Extend class loaders, And Apply Class loaders

7.4.3 Thread Context class loader

Context Class loader, available from java.lang.thread. The parental delegation model does not solve all of the classloader problems encountered in Java application development. For example, Java provides a number of Service Provider interfaces (SPIs) that allow third parties to provide Interface implementations. Common SPIs are JDBC, JCE, JNDI, JAXP, and so on. The SPI interface is provided by the core library and loaded by the boot class loader. The third party implementation is implemented by the application class loader. There is no concrete implementation of SPI at this point. The SPI interface code uses a thread context classloader. The thread context classloader defaults to the application classloader.

Chapter 8 Virtual machine bytecode execution engine

8.1 an overview of the

A VM is a concept relative to a physical machine. The execution engine of a physical machine is built directly on top of the processor, cache and instruction set operating system. The execution engine of virtual machine is built on software, not limited by physical conditions, customized instruction set and execution engine. In a virtual machine implementation, the execution process can be interpreted execution and compiled execution, either individually or in combination. But all virtual machine engines, in terms of a common Facade, are binary streams of input bytecode that parse execution and output execution results.

This chapter introduces virtual machine method calls and bytecode execution from a conceptual perspective.

8.2 Runtime stack frame structure

The Java virtual machine has methods as its most basic unit of execution. Each method is executed with a Stack Frame. The stack frame is also the stack element of the virtual machine stack. A stack frame stores information about a method’s local variogram, operand stack, dynamic linkage, and method return address. The size of the local variator table required for a stack frame, and the depth of the operand stack required, are written in the Code property published by the party when it is encoded into bytecode.Code:  stack=2, locals=1, args_size=1So how much memory a stack frame needs to allocate is determined before it runs, depending on the source code and the virtual machine’s own implementation.

8.2.1 Local variation scale

The minimum capacity unit of a local Variable table is Variable Slot. According to the Java Virtual Machine Specification, a Variable Slot can store a Boolean, byte, CHAR, init, float, Reference, or returnAddress data. 32-bit systems can be 32 bits, and 64-bit systems can be 64 bits to implement a variable slot. For 64-bit data types (long and double), two consecutive variable slots are allocated in high-aligned fashion. Because it is thread private, there is no thread-safety issue whether the reads and writes to the two continuous variable slots are atomic or not.

From parameters to parameter list

When a method is called, the parameter values are put into the local variable table. The class method parameter Slot starts at 0. The instance method argument Slot starts at 1, and Slot0 is given to this, which points to the instance. We compare the bytecode of a class method with that of an instance method.

public static int add(int a, int b) {returna + b; }public int remove(int a, int b) {returna - b; }Copy the code
public static int add(int.int);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0     a   I
            0       4     1     b   I

  public int remove(int.int);
    flags: ACC_PUBLIC
    Code:
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0  this   Lexecute/Reerer;
            0       4     1     a   I
            0       4     2     b   I
Copy the code

Variable slot multiplexing

When the scope of a variable is smaller than the whole method body, the variable slot can be reused to save stack memory space. Such as {},if(){} code block. Variable slot multiplexing has a “minor side effect” of memory reclamation.

public static void main(String[] args) {{byte[] placeholder = new byte[64 * 1024 * 1024];
        }
        System.gc();
}

// Execution result
[GC (System.gc())  69468K->66040K(251392K), 0.0007701 secs]
[Full GC (System.gc())  66040K->65934K(251392K), 0.0040938 secs]

// Explanation: While the scope of the placeholder is limited, the local variable tables still reference the placeholder and cannot be reclaimed.
Copy the code
public static void main(String[] args) {{byte[] placeholder = new byte[64 * 1024 * 1024];
        }
        int i=0;
        System.gc();
}
// Execution result
[GC (System.gc())  69468K->66040K(251392K), 0.0007556 secs]
[Full GC (System.gc())  66040K->398K(251392K), 0.0044978 secs]

Int I =0 (placeholder); int I =0 (placeholder); int I =0 (placeholder);
Copy the code
public static void main(String[] args) {{byte[] placeholder = new byte[64 * 1024 * 1024];
            placeholder=null;
        }
        System.gc();
}
// Execution result
[GC (System.gc())  69468K->66088K(251392K), 0.0022762 secs]
[Full GC (System.gc())  66088K->398K(251392K), 0.0050265 secs]
// explain the active release placeholder
Copy the code

Local variable assignment

Class variables are assigned a default value of zero during preparation. Local variables have no preparation phase. Therefore, the following code fails to compile. Even if the compile passes, it will be detected during the verification phase, resulting in class loading failure.

 public static void fun4(a){
     int a;
     
     // Compilation failed, Variable 'a' might not have been initialized
     System.out.println(a);
}
Copy the code

8.2.2 Operand stack

Bytecode instructions read and write Operand stacks. The data types of the elements in the operand stack must exactly match the instruction sequence. Both the compile phase and the class validation phase ensure this. In most virtual machine implementations, the operand stack of the upper stack frame overlaps with the local variables of the lower stack frame. This saves space and, importantly, directly public the data during method calls without the need for external parameter copying.

8.2.3 Dynamic Connection

During class loading, symbolic references are resolved to direct references. Method call instructions take symbolic references from the constant pool as arguments. Some of these method symbolic references are converted to direct references when the class is loaded or used for the first time, which is called static resolution. The other part needs to be converted to a direct reference during each run, which is called a dynamic join.

8.2.4 Method Returns the address

Normal call completion and exception call completion. Restores the execution state of the calling method.

8.3 Method Invocation

Five method invocation instructions in a Java virtual machine:

  • Invokestatic, invokes static methods.
  • Invokespecial calls the instance constructor () method, the private method, and the methods in the parent class.
  • Invokevirtual, call virtual methods?
  • Invokeinterface, which invokes the interface method, determines, at run time, an implementation object for that interface.
  • Invokedynamic, which dynamically resolves the method referenced by the call point qualifier at runtime and then executes that method. The first four instruction logic is solidified inside the virtual machine, while the dispatch logic of the InvokeDynamic instruction is determined by the user-specified bootstrap method.

Methods according to whether the class loading stage can be converted into direct reference classification can be divided into:

  • Non-virtual Method: in the class loading phase, symbolic references are resolved to direct references to the Method.
    • Including static methods that can be called by InvokeStatic,
    • Includes instance constructors, private methods, and superclass methods that can be called by Invokespecial
    • The final modifier method (although it uses invokevirtual calls), this type of method cannot be overridden, there are no more options, and it is unique.
  • Virtual methods, other methods.

8.3.1 parsing

The invocation of non-virtual methods is called Resolution,” compiler-aware, run-time immutable “, that is, the class-loading phase converts symbolic references to direct references. The other method is called Dispatch.

8.3.2 dispatch

Dispatch, “Dispatch,” is static or dynamic, single Dispatch or multiple Dispatch. Overloaded or overridden methods with the same name appear. The selection of methods with the same name I can call dispatch

1. Static dispatch

Method Overload Resolution, which is actually called Method Overload Resolution. Static dispatch occurs at compile time. So let’s look at a little bit of code, the sayHello method overload.

// The method is statically dispatched
public class StaticDispatch {

    static abstract  class Human{}

    static class Man extends Human{}

    static class Woman extends Human{}

    public static void  sayHello(Man man){System.out.println("hello,gentleman!"); }

    public static void sayHello(Human guy){ System.out.println("hello,guy!"); }public static  void  sayHello(Woman women){System.out.println("hello,lady!"); }public static void main(String[] args) {
        Human man=new Man();
        Human woman=new Woman();
        StaticDispatch dispatch=newStaticDispatch(); dispatch.sayHello(man); dispatch.sayHello(woman); }}// Execution result:
hello,guy!
hello,guy!
Copy the code

Corresponds to Class bytecode

public static void main(java.lang.String[]);
    Code:
      stack=2, locals=3, args_size=1
         0: new           #7                  // class execute/StaticDispatch$Man
         3: dup
         4: invokespecial #8                  // Method execute/StaticDispatch$Man."<init>":()V
         7: astore_1
         8: new           #9                  // class execute/StaticDispatch$Woman
        11: dup
        12: invokespecial #10                 // Method execute/StaticDispatch$Woman."<init>":()V
        15: astore_2
        16: new           #11                 // class execute/StaticDispatch
        19: dup
        20: invokespecial #12                 // Method "<init>":()V
        23: astore_3
        24: aload_3
        25: aload_1
        26: invokevirtual #13                 // Method sayHello:(Lexecute/StaticDispatch$Human;) V
        29: aload_3
        30: aload_2
        31: invokevirtual #13                 // Method sayHello:(Lexecute/StaticDispatch$Human;) V
        34: return
Copy the code

In lines 0 to 15, we build Man objects and Woman objects and place them in the local variable table. Line 26, execute Method sayHello (Lexecute/StaticDispatch Human;) V, line 31, execution method MethodsayHello: (Lexecute/StaticDispatchHuman;) V, line 31, the execution Method sayHello (Lexecute/StaticDispatchHuman;) V, line 31, execution method MethodsayHello: (Lexecute/StaticDispatchHuman;) V, sayHello(Human) is actually executed. Instead of “sayHello” (Man) or “sayHello” (Woman). There are two types involved:

  • We may also say that Static types are Apparent types
  • The Actual Type, or Runtime Type, is Man, Woman

The actual type of the object is not known at compile time, so methods are dispatched according to the static type of the object.

2. Dynamic dispatch

Closely related to Override. Dynamic dispatch occurs at run time. At run time, determine the receiver of the method (the object to which the method belongs)

// The method is dispatched dynamically
public class DynamicDispatch {

    static abstract class Human {
        public void sayHello(a) {System.out.println("hello,guy!");}
    }

    static class Man extends Human {
        public void sayHello(a) {System.out.println("hello,gentleman!"); }}static class Woman extends Human {
        public void sayHello(a) {System.out.println("hello,lady!");}
    }

    public static void main(String[] args) {
        Human man = new Man();
        Human woman = new Woman();
        man.sayHello();//hello,gentleman!
        woman.sayHello();//hello,lady!
        man = new Woman();
        man.sayHello();//hello,lady!}}// Execution result:
hello,gentleman!
hello,lady!
hello,lady!
Copy the code

Corresponding bytecode

0: new   #2  // class execute/DynamicDispatch$Man
3: dup
4: invokespecial #3  // Method execute/DynamicDispatch$Man."<init>":()V
7: astore_1
8: new   #4  // class execute/DynamicDispatch$Woman
11: dup
12: invokespecial #5  // Method execute/DynamicDispatch$Woman."<init>":()V
15: astore_2
16: aload_1
17: invokevirtual #6  // Method execute/DynamicDispatch$Human.sayHello:()V
20: aload_2
21: invokevirtual #6  // Method execute/DynamicDispatch$Human.sayHello:()V
24: new   #4  // class execute/DynamicDispatch$Woman
27: dup
28: invokespecial #5  // Method execute/DynamicDispatch$Woman."<init>":()V
31: astore_1
32: aload_1
33: invokevirtual #6  // Method execute/DynamicDispatch$Human.sayHello:()V
36: return
Copy the code

At line 7, astore_1 stores the Man object at line 15, and astore_2 stores the Woman object at line 16 and 17,aload_1,invokevirtual. The actual call is man.sayHello (), line 20, line 21,aload_2,invokevirtual. The actual call is woman.sayHello () at line 31, where astore_1 stores the Woman object at line 32, line 33,aload_1,invokevirtual. What is actually called is the woman.sayHello () method

At run time, the selection is to dispatch methods based on the actual type of the man and woman objects.

Tip: Fields never participate in polymorphism, and property names accessed in methods are always properties of the current class. A subclass overshadows a field of the same name in its parent class

3. Single dispatch and multiple dispatch

Case of a method: receiver of a method and argument of a method single dispatch: dispatch based on one case multiple dispatch: dispatch based on multiple cases. The current Java language is a static multi-dispatch, dynamic single-dispatch language. Compile-time determines symbolic references to a method based on the method receiver and parameters. The runtime parses and executes symbolic references based on the receiver of the method.

Consider the following code

public class Dispatch {

    static class Father{
        public  void f(a) {System.out.println("father f void"); }public  void f(int value) {System.out.println("father f int");}
    }
    static  class Son extends Father{
        public  void f(int value) {System.out.println("Son f int"); }
        public  void f(char value) { System.out.println("Son f char");}
    }

    public static void main(String[] args) {
        Father son=new Son();
        son.f('a'); }}// Result: Son f int
Copy the code

The bytecode

0: new           #2                  // class execute/Dispatch$Son
3: dup
4: invokespecial #3                  // Method execute/Dispatch$Son."<init>":()V
7: astore_1
8: aload_1
9: bipush        97
11: invokevirtual #4                  // Method execute/Dispatch$Father.f:(I)V
Copy the code

F (I)V = Father (int); f(I)V = Father (int); The second is the runtime, the receiver is Son,Son has overwritten f:(I)V. So the result is son.f :(I)V

4. Implementation of dynamic VM dispatch

Virtual method table, interface method table, type inheritance analysis, daemon inline, inline cache

8.4 Dynamically typed language support

8.4.1 Dynamically typed languages

Key feature of dynamically typed languages: the main process of type checking is at runtime, not at the compiler. Groovy, JavaScript, Lisp, Lua, Python. Statically typed languages: compilers do type checking, such as C++ and Java.

8.4.2 Java and dynamic typing

The Java virtual machine needs to support dynamically typed languages, so publish the InvokeDynamic instructions in JDK7.

8.4.3 Java. Lang. Invoke the package

slightly

8.4.4 invokedynamic instruction

slightly

8.5 Stack – based bytecode interpretation execution engine

8.5.1 Explaining the Execution

slightly

8.5.2 Stack-based Instruction Set Register-based instruction set

slightly

8.5.3 Stack-based interpreter execution process

public int calc(a) {
    int a = 100;
    int b = 200;
    int c = 300;
    return (a + b) * c;
}
Copy the code
stack=2, locals=4, args_size=1
         0: bipush        100   // push the constant 100 to the top of the operand stack
         2: istore_1		    // The top element (100) is stored in slot 1, and the top element is consumed
         3: sipush        200   // push the constant 200 to the top of the operand stack
         6: istore_2            // The top of the stack element (200) is stored in slot 2, and the top of the stack element is consumed
         7: sipush        300   // push the constant 300 to the top of the operand stack
        10: istore_3            // The top of stack element (300) is stored in slot 3, and the top of stack element is consumed
        11: iload_1             // push the local variable slot1 value 100 to the top of the operand stack
        12: iload_2             // push the local variable slot2 value 200 to the top of the operand stack
        13: iadd                // Consume 100 and 200 at the top of the stack to get 300 and push it to the top
        14: iload_3				// push the local variable slot3 value 300 to the top of the operand stack
        15: imul				// Consume 300 and 300 at the top of the stack to get 90000 and push it to the top
        16: ireturn             // consume 90,000 at the top of the stack, and the integer result is returned to the method caller
Copy the code

Chapter 10 front-end compilation and optimization

10.1 an overview of the

To clarify a few concepts, a JIT Compiler (Just In Time Compiler) is the process of turning bytecode into native code at runtime. AOT Compiler (AOT Compiler, Ahead Of Time Compiler), the process Of compiling a program directly into binary code relevant to the target and its instruction set.

The “front-end compiler” discussed here refers to the process of converting *.java files into *.class files, primarily the Javac compiler.

10.2 Javac Compiler

10.2.1 introduction

The Javac compiler is written in the Java language. Analyzing the overall structure of Javac code, the compilation process can be roughly divided into one preparation process and three processing processes. As follows:

  • 1) Preparation: Initialize the plug-in annotation handler
  • 2) Parse and populate the symbol table
    • Lexical and grammatical analysis. An abstract syntax tree is constructed by converting the character stream of the source code into a tag set.
    • Populate the symbol table. Generates symbolic addresses and symbolic information.
  • 3) Annotation processing by plug-in annotation processor
  • 4) Analysis and bytecode generation
    • Label check. Check for static information about the syntax.
    • Data flow and control flow analysis. Check the dynamic running process of the program
    • Solution sugar. Restore the syntactic sugar code to its original form
    • Bytecode generation. Convert the information generated in the previous steps into bytecode.

If annotation processing produces a new symbol, the parsing and filling process is repeated.Javac compile action entry com. Sun. View Javac. Main. JavaCompiler class. Compile (),compile2()

10.2.2 Parsing and Populating the symbol Table

1. Lexical and grammatical analysis

Corresponding parserFiles() method lexical analysis: the process by which source code character streams are converted into collections of tokens. The tag is the smallest element at compile time. Keywords, variable names, literals, and operators are all acceptable tokens. For example, “int a = b + 2” contains six tokens, int, a, =, b, +, 2. Lexical analysis by com. Sun. View javac. Parser. The Scanner. Parsing: The process of constructing an abstract syntax tree from a sequence of tags. Abstract Syntax Tree (AST), a Tree representation describing the Syntax structure of code. Each node of the Tree represents a Syntax structure, such as package, type, operator, interface, return value, and so on. Com. Sun. View javac) parser) parser implementation. The abstract syntax tree is based on com. Sun. View javac. Tree. JCTree said. Subsequent operations build on the abstract syntax tree.

2. Fill in the symbol table

Corresponds to the enterTree() method.

10.2.3 Annotation Processor

JDK6, JSR-269 proposal, “Plug-in Annotation Processor” API. Processing specific annotations ahead of compile time can be thought of as a compiler plug-in that allows you to read, modify, and add arbitrary elements to the abstract syntax tree. If changes are made, the compiler will go back to parsing and populating the symbol table and process them again until no changes are made. Each loop is called a Round. There are many things you can do with an annotation handler, such as Lombok, which automatically generates getter/setter methods, null checks, and equals() and hashCode() methods through annotations.

10.2.4 Semantic analysis and bytecode generation

Abstract syntax trees can represent a correct source program, but do not guarantee logical semantics. The main tasks of semantic analysis are type check, control flow check, data flow check and so on. For example,

int a = 1;
boolean b = false;
char c = 2;

// All possible subsequent operations can generate abstract syntax trees, but only the first one can pass semantic analysis
int  d= a + c;
int  d= b + c;
char d= a + c;
Copy the code

Most of the red line errors you see in the IDE come from the semantic analysis phase.

1. Check labels

The attribute() method checks whether the variable was declared before it was used, whether the variable matches the assigned data type, and so on. The definitions of three variables belong to annotation checking. Annotation checking is done with minimal optimizations, such as Constant Folding.

int a = 1 + 2; It actually folds into a literal."3"Copy the code

2. Data and control flow analysis

The flow() method, which the context logic further verifies, such as whether the method has a return value for each path, whether the numeric operation type is reasonable, and so on.

3. Solution sugar

Syntactic Sugar (Syntactic Sugar), programming term Peter J.Landin. Reduce the amount of code, increase program readability. Examples include generics in the Java language (generics in other languages are not necessarily syntactic sugar implementations, such as C# generics directly supported by CLR), variable length arrays, auto-boxing and unboxing, etc. Parsing sugar, which converts sugar syntax into the original base syntax at compile time.

4. Bytecode generation

  • Convert the previously generated information (syntax tree, symbol table) into bytecode,
  • Small code additions,(),(), and so on
  • A small amount of code optimizes the conversion, replacing string concatenation with StringBuffer or StringBuilder, and so on.

10.3 Java Syntax sugar

10.3.1 generic

1. Java generics

In JDK5, Java’s implementation of Generics is called Type Erasure Generic, as opposed to C#’s Reified Generics, where C# Generics are represented both in the source code and in compiled intermediate languages (where Generics are a placeholder). List and List are two different types. Java generics, on the other hand, exist only in the source code. After compilation, they become a unified type, called type erasure, and a cast instruction will be added in use.

Map<String, String> stringMap = new HashMap<String, String>();
stringMap.put("hello"."Hello");
System.out.println(stringMap.get("hello"));

Map objeMap = new HashMap();
objeMap.put("hello2"."Hello 2");
System.out.println((String)objeMap.get("hello2"));
Copy the code

Intercept part of the bytecode

0: new           #2                  // class java/util/HashMap
4: invokespecial #3                  // Method java/util/HashMap."<init>":()V
13: invokeinterface #6.3            // InterfaceMethod java/util/Map.put:(Ljava/lang/Object; Ljava/lang/Object;) Ljava/lang/Object;
25: invokeinterface #8.2            // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;) Ljava/lang/Object;
30: checkcast     #9                  // class java/lang/String
33: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;) V
        
36: new           #2                  // class java/util/HashMap
40: invokespecial #3                  // Method java/util/HashMap."<init>":()V
49: invokeinterface #6.3            // InterfaceMethod java/util/Map.put:(Ljava/lang/Object; Ljava/lang/Object;) Ljava/lang/Object;
61: invokeinterface #8.2            // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;) Ljava/lang/Object;
66: checkcast     #9                  // class java/lang/String
69: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;) V

72: return
Copy the code

You can see that the two parts of the code are the same after compilation. At line 0, new HashMap<String, String>() actually constructs Java /util/HashMap. At line 30, stringmap. get(“hello”), the checkcast directive, does a type conversion

2. Historical background

2004, Java5.0. In order for code to be “binary backward compatible,” the original code must be able to compile and run after the introduction of generics. For example, Java arrays support covariation, and collection classes can store elements of different types. The following code

Object[] array = new String[10]; 
array[0] = 10; // There will be no problems at compile time, but an error will be reported at run time

ArrayList things = new ArrayList(); 
things.add(Integer.valueOf(10)); // No errors are reported when compiling or running
things.add("hello world");
Copy the code

If you want to ensure that the above code will still run after the introduction of generics in Java5.0, you have two options:

  • The original type that needs to be generalized remains the same, and a new set of generic type versions is added. Generic tools now, such as the c # added a set of System. Collections. Generic new container, the original System. Collections remains the same.
  • The type that needs to be generified in situ is genericized. Java5.0 uses in-situ generization as type erasure.

The main reason why C# was different from Java was that C# was only 2 years old and Java was almost 10 years old. Type erasure is technical debt left over from laziness.

3. Type erasure

Type erasure has other drawbacks besides the previously mentioned conversion to uniform bare types and type checking and conversion at use. 1) Primitive Type data generics are not supported. ArrayList needs to use its corresponding reference Type ArrayList, resulting in unboxing read and write. 2) Generic type information is not available at runtime, for example

public  <E> void doSomething(E item){
        E[] array=new E[10];  // It is illegal to use generics to create arrays
        if(item instanceof  E){}// Invalid, cannot be used for instance judgment on generics
}
Copy the code

When we write a List to array conversion method, we need to pass in an additional array component type

 public  static <T> T[] convert(List<T> list,Class<T> componentType){
        T[] array= (T[]) Array.newInstance(componentType,list.size());
        for (int i = 0; i < list.size(); i++) {
            array[i]=list.get(i);
        }
        return array;
}
Copy the code

3) Type conversion problems.

// Failed to compile
ArrayList
      
        is not a subclass of ArrayList
       , although String is a subclass of Object.
      
ArrayList<Object> list=new ArrayList<String>();
Copy the code

To support covariant and contravariant generics introduce extends, super

/ / covariant
ArrayList<? extends Object> list = new ArrayList<String>();

/ / inverter
ArrayList<? super String> list2 = new ArrayList<Object>();
Copy the code

Value types and future generics

In 2014, Oracle, Valhalla language improvement project as part of the new generic implementation

10.3.2 other

Auto-boxing, auto-unboxing, traversal loops, variable-length arguments, conditional compilation, inner classes, enumerated classes, numeric literals, switch, try, and more.

10.3.3 * Extended reading

Java covariant introduces Lambda and Invokedynamic

10.4 Live Lombok annotation processor

Chapter 11 back-end compilation and optimization

11.1 an overview of the

The previous chapter looked at the process from *.java to *.class, the source code to bytecode. This chapter covers the process from binary bytecode to target machine code, with two types of just-in-time and just-in-time compilers.

11.2 Real-time compiler

In the current two mainstream commercial Java virtual machines (HotSpot and OpenJ9), Java programs are initially interpreted through the Interpreter. When the virtual machine detects that a method or block of code is being run too frequently, Will put these Code recognition as a “Hot Spot” Code (Hot Spot Code), in order to improve the execution efficiency of Hot Code, at run time, the virtual machine will compile the Code to machine Code, cost and in a variety of means as much as possible to optimize the Code and run time to complete the task of the back-end compiler is called just-in-time compilers.

11.3 Advance compiler

  • Just-in-time compilation consumes time that could have been used to run the program and consumes computing resources that could have been used

Resources for the program to run,

  • Cache acceleration for the just-in-time compiler to improve the startup time of Java programs

And need a period of time after warm-up to reach the highest performance problems. This precompilation is called Dynamic AOT or simply JIT Caching.

Android VIRTUAL Machine history: Even if the compiler Android4.4 starts the Art VIRTUAL machine ahead of time, resulting in the need to compile the App during installation, which is very time-consuming, but the running performance is improved. Precompile automatically when the system is idle.

11.3 Compiler optimization techniques

slightly

Chapter 12 Java Memory model and threads

12.1 an overview of the

This section describes how to implement multi-threading in virtual machines, and a series of problems and solutions caused by data sharing between multi-threads

12.2 Hardware Efficiency and Consistency

Before introducing the Java VIRTUAL machine (JVM) memory model, understand the concurrency problems of physical machines.

  • Hardware efficiency. In addition to processor computation, computer processing tasks also have memory interaction, that is, reading and writing data. The storage device and the processor run several orders of magnitude different, so the introduction of read and write speed as close as possible to the processor Cache. The processor reads and writes to the cache, which synchronizes the data to memory.
  • Cache consistency issues. In a shared-memory multi-core system, each processor has its own cache and shares the same main memory. To address consistency issues, processors need to follow protocols such as MSI, MESI, MISI,Synapse,Dragon Protocol, and others when accessing the cache.
  • Out-of-order code execution optimization problem. To improve computing efficiency, the processor may not execute the data sequentially. However, in single-thread mode, the processor ensures that the execution results are consistent with the sequential execution results. In the case of multiple threads, there is no guarantee that multiple tasks will be executed in sequence.

Java virtual machines have their own memory model and also have problems with physical machine types.

12.3 The Java Memory Model

12.3.1 overview

The Java Memory model states that variables are stored in Main Memory, and threads have their own working Memory, which holds a copy of variables in Main Memory. Threads can only read and write variables in Working Memory. Variables shared between threads need to be shared in the main Memory. Execution processing for the JVM memory model will revolve around solving two problems:

  • Working memory data consistency
  • Instruction reordering optimization, compile-time reordering and run-time reordering.

12.3.2 Memory Interactive Operations

The protocol for interaction between main memory and working memory defines the following operations, which the Java VIRTUAL machine must ensure are atomic.

  • Lock, which acts on main memory variables and identifies them as thread-exclusive so that other threads cannot lock
  • Unlock, which acts on a main memory variable to unlock a thread
  • Read, applied to the main memory variable, transfers the value of the variable to working memory, while subsequent loads are used
  • Load, applied to a working memory variable, puts the value of read into a copy of the working memory variable.
  • Use, applied to working memory variables whose values are passed to the execution engine
  • Assign, which works on working memory variables. The execution engine assigns values to variables in working memory
  • Store, which acts on working memory variables whose values are transferred to main memory for subsequent write use
  • Write, applied to the main memory variable, puts the value of the store variable into the main memory variable.

To copy variables from main memory to working memory, read and load must be executed sequentially, but not necessarily consecutively. To synchronize variables from working memory to main memory, store and write must be executed sequentially, but not necessarily consecutively.

12.3.3 Memory Model Running Rules

1. Three features of basic memory interaction operations

The Java memory model is built around how these three features are handled during concurrency, ultimately to achieve consistency in shared variables across multiple working memory, and to allow programs to run as expected in concurrency.

  • Atomicity, which means that one or more operations are either not performed, or they are all performed without interruption
  • Visibility. When multiple threads access the same variable, one thread changes the value of the variable and the other threads immediately see the changed value. Threads achieve visibility by sharing main memory.
  • Order, as-if-serial, inter-thread, synchrinized code and volatile fields must be kept in order

2. Antecedent principle

happens-before

  • Procedural order rule
  • Pipe lock rules
  • Volatile variable rule
  • Thread start rule
  • Thread termination rule
  • Thread interrupt rule
  • Object finalization rule
  • transitivity

3. Memory barrier

A memory barrier is an instruction that is inserted between two CPU instructions to prevent instruction reordering of processor instructions.

12.3.4 Volatile Variables

Volatile has two main semantics

Semantics 1 guarantees visibility

This ensures the memory visibility of operations on volatile variables by different threads, but is not the same as the safety of concurrent operations

  • Assign-store-write for volatile variables must occur consecutively:
    • Change the value of a volatile copy in working memory
    • Flush the changed copy value to main memory
  • Read-load-use must occur consecutively for a thread to read volatile variables:
    • Read volatile values from main memory and store worker thread copies
    • Reads a copy of a variable from working memory

Semantic 2 disallows instruction reordering

The volatile scenario is summed up as “write once, read everywhere,” in which one thread updates variables while another reads variables and performs logic based on their new values, such as status flag bit updates and observer model variable value Posting