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:
-
- 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:
-
- 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=1
So 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
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