Complete JVM learning notes please click here

Execution Engine Overview

  • The execution engine is one of the core components of the Java Virtual Machine
  • Virtual machine is a relative to the concept of “physical machine”, the two machines have code execution ability, the difference is the physical machine execution engine is directly based on CPU, cache, instruction set, and the operating system level, and virtual machine execution engine is implemented by the software itself, so you can not is conditioned by the physical conditions custom instruction set and execution engine structure system, The ability to execute instruction set formats that are not directly supported by hardware.
  • The main job of the JVM is to load bytecode into the JVM itself, but the bytecode cannot run directly on the operating system because bytecode instructions are not equivalent to local machine instructions, but contain only bytecode instructions, symbol tables, and other auxiliary information that can be recognized by the JVM lock
  • So, if you want a Java program to run, the execution engine’s job is to translate/compile bytecode instructions into local machine instructions on the corresponding platform. Simply put, the execution engine in the JVM acts as a translator for translating high-level languages into machine languages.
  • Execute the working process of the engine
    • From the outside, all Java virtual machines have the same input and output of the execution engine: the input is a binary stream of bytecode, the processing is the equivalent process of bytecode parsing, and the output is the execution result.


1) Exactly what bytecode instructions the execution engine needs to execute during execution depends entirely on the PC register.





2) After each instruction operation, the PC register will update the address of the next instruction to be executed.





3) Of course, during the execution of the method, the execution engine may accurately locate the object instance information stored in the Java heap through the object reference stored in the local variable table, and locate the type information of the target object through the metadata pointer in the object header.

Java code compiles and executes procedures

Most program code needs to go through the following steps before it can be converted into object code of the physical machine or the instruction set executed by the virtual machine:

Java code compilation is done by the Java source compiler, as shown in the following flowchart:

Java bytecode execution is done by the JVM execution engine, as shown in the following flowchart:

What is an Interpreter and what is a JIT compiler?

Interpreter: When the Java virtual machine starts, the bytecode is interpreted line by line according to predefined specifications, and the contents of each bytecode file are “translated” into local machine instructions for the corresponding platform. JIT (Just In Time Compiler) : This is when the virtual machine compiles the source code directly into the machine language specific to the local machine platform.

Why is Java a half-compiled, half-interpreted language?

In the JDK1.0 era, it was more accurate to define the Java language as “interpreted execution.” More recently, Java also developed compilers that could generate native code directly.

JVM execution of Java code now typically involves a combination of interpreted execution and compiled execution.

Machine code, instruction, assembly language

Machine code

  • The various instructions expressed in binary code are called == machine instruction code ==. From the beginning, people used it to write programs. It was called machine language.
  • Although machine language can be understood and accepted by computers, it is too different from human language to be easily understood and remembered by people, and it is easy to make mistakes in programming.
  • As soon as a program written in it is entered into the computer, the CPU reads and runs it directly, so it is the fastest compared to programs written in other languages.
  • Machine instructions are closely related to the CPU, so different kinds of cpus have different machine instructions.

instruction

  • Instructions were invented because machine code was a binary sequence of zeros and ones, which was very unreadable.
  • Instruction is to simplify the specific sequence of 0 and 1 in machine code into corresponding instruction (generally abbreviated as English, such as MOV, inc, etc.), which is slightly more readable
  • Because different hardware platforms may have different machine codes for performing the same operation, the same instruction (such as MOV) on different hardware platforms may also have different machine codes.

Instruction set

  • Different hardware platforms support different instructions. Therefore, the instructions supported by each platform are called the instruction set of the corresponding platform.
  • Such as common
    • The x86 instruction set, which corresponds to the x86 architecture platform
    • ARM instruction set, corresponding to ARM architecture platform

Assembly language

  • Because the instructions were still too unreadable, assembly language was invented.
  • In assembly language, Mnemonics are used to replace the opcodes of machine instructions, and address symbols or labels are used to replace the addresses of instructions or operands.
  • In different hardware platforms, assembly language corresponds to different machine language instruction sets, which are converted into machine instructions through assembly process.
    • Since the computer only knows the instruction code, the program written in assembly language must be translated into machine instruction code before the computer can recognize and execute it.

A high-level language

  • To make programming easier for computer users, various high-level computer languages have emerged. High-level language is closer to human language than machine language or assembly language
  • When a computer executes a program written in a high-level language, it still needs to interpret and compile the program into the machine’s instructions. The program that does this is called an interpreter or compiler.

The bytecode

  • Bytecode is a binary code (file) of intermediate state (intermediate code) that is more abstract than machine code and needs to be translated by a translator to become machine code
  • Bytecode is mainly used to implement specific software operation and software environment, and has nothing to do with hardware environment.
  • Bytecode is implemented through compilers and virtual machines. The compiler compiles the source code into bytecode, which is translated into instructions that can be executed directly by the virtual machine on a particular platform.
    • A typical use of bytecode is Java Bytecode

C, C++ source program execution process

The compilation process can be divided into two phases: compilation and assembly.

  • Compilation process: it is to read the source program (character stream), analyze its morphology and syntax, and convert high-level language instructions into functional equivalent assembly code
  • Assembly process: Actually the process of translating assembly language code into target machine instructions.

The interpreter

JVM designers were motivated solely by the cross-platform nature of Java programs, avoiding static compilation to generate native machine instructions directly, and thus the idea of implementing an interpreter that interprets bytecode execution line by line at runtime.

  • The interpreter’s true role is as a runtime “translator”, “translating” the contents of a bytecode file into local machine instructions for the corresponding platform.
  • After a bytecode instruction is interpreted, the next bytecode instruction to be interpreted is recorded in the PC register.

In the history of Java, there have been two sets of interpreters, the ancient == bytecode interpreter == and the now-ubiquitous == template interpreter ==.

  • The bytecode interpreter simulates bytecode execution through pure software code, which is very inefficient. · – The template interpreter associates each bytecode with a template function, which directly generates the machine code for the execution of the bytecode, thus greatly improving the performance of the interpreter.
    • In HotSpot VM, the Interpreter consists mainly of the Interpreter module and the Code module.
      • The Interpreter module: implements the core functions of the Interpreter
      • Code module: Used to manage the local machine instructions generated by the HotSpot VM at runtime

The status quo

  • Because the interpreter is so simple in design and implementation, there are many high-level languages besides the Java language that are also executed based on the interpreter, such as Python, Perl, Ruby, and so on. But today, interpreter-based execution is a byword for inefficiency and is often mocked by SOME C/C+ + programmers.
  • To address this problem, the JVM platform supports a technique called just-in-time compilation. The purpose of just-in-time compilation is to avoid the function being interpreted. Instead, the whole function body is compiled into machine code, and only the compiled machine code is executed each time the function is executed. This method can greatly improve the execution efficiency.
  • Nevertheless, the interpreter-based execution model has made an indelible contribution to the development of intermediate languages.

The JIT compiler

Why does the HotSpot VM interpreter coexist with the JIT compiler

Java code execution classification:

  • The first is to compile the source code into a bytecode file and then convert the bytecode file to machine code for execution through the interpreter at run time
  • The second is compilation execution (directly compiled into machine code). Modern virtual machines use just-in-time (JIT) compilation techniques to compile methods into machine code and then execute them to improve execution efficiency

HotSpot VM is one of the representative high performance virtual machines in the market. It uses the == interpreter and just-in-time compiler architecture ==. As the Java Virtual machine runs, the interpreter and the just-in-time compiler can work together to learn from each other and try to choose the best way to balance the time it takes to compile native code against the time it takes to interpret the executing code directly. Today, The performance of Java programs has changed so much that it can compete with C/C++ programs.

The interpreter is still necessary

Some developers may wonder why they need an interpreter to “slow down” performance when HotSpotVM already has a built-in JIT compiler. The JRockit VM, for example, does not contain an interpreter, and the bytecode is all compiled and executed by the just-in-time compiler.

Let’s be clear: when a program is started, the interpreter can come into play immediately, saving compilation time and executing immediately. For a compiler to be effective, it takes a certain amount of execution time to compile code into native code. However, when compiled into native code, the execution is efficient.

So: While the execution performance of programs in JRockitVM will be very efficient, the program will inevitably take longer to compile at startup. For server applications, startup time is not a major concern, but for applications where startup time is important, an interpreter and just-in-time compiler architecture may be needed to strike a balance. In this mode, == when the Java virtual machine starts, the interpreter can take effect first rather than wait for the just-in-time compiler to complete, saving a lot of unnecessary compilation time. Over time, compilers come into play, putting more and more code into native code for more efficient execution. = =

At the same time, explain execution acts as the compiler’s “escape door” when radical optimization by the compiler is not possible.

HostSpot Execution mode of the JVM

When the virtual machine starts up, the interpreter can take effect first instead of waiting for the just-in-time compiler to complete, saving a lot of unnecessary compile time. And as the program runs, the just-in-time compiler gradually comes into play, == according to the hot spot detection function, the valuable bytecode is compiled into local machine instructions, in exchange for higher program execution efficiency. = =

case

Note the subtle dialectic between the online environment of explain execution and compile execution. The machine can bear more load in the heat state than in the cold state. If the flow is cut at the same rate as the flow in the hot engine state, the server in the cold engine state may die because it cannot bear the flow.

In production environment publishing, publishing is done in batches, divided into batches based on the number of machines, with each batch accounting for up to 1/8 of the cluster. There has been such a case of failure: a programmer in the publishing platform to publish in batches, when entering the total number of batches, mistakenly fill in the component as two batches. Under normal circumstances, half of the machines can barely handle traffic, but because the JVM that has just started is interpreted execution, hot code statistics and JIT dynamic compilation have not been carried out, resulting in the machine startup, the current half of the successful release of the server immediately down, this failure indicates the existence of JIT. Ali Team

The JIT compiler

Concept to explain

  • A “compiler” in the Java language is an “indeterminate” operation, as it may refer to a == front end compiler == (a “compiler front end” is more accurate) that converts a. Java file into a. Class file.
  • It may also refer to the process by which the virtual machine’s == back-end runtime Compiler == (JIT Compiler, Just In Time Compiler) converts bytecode into machine code.
  • It may also refer to the process Of compiling. Java files directly into native machine code using the == static AOT Compiler == (AOT Compiler, Ahead Of Time Compiler).
Front-end compilers: Sun’s Javac, incremental compiler (ECJ) in Eclipse JDT


JIT compilers: C1 and C2 compilers for HotSpot VM.


AOT compilers: GNU Compiler for the Java (GCJ), Excelsior JET.

Hotspot code and detection mode

Of course, whether you need to start the JIT compiler to compile bytecode directly into local machine instructions for the platform depends on how often the code is called to execute. For bytecodes that need to be compiled into native code, also known as “hot code”, the JIT compiler will optimize frequently called “hot code” at run time and compile it directly into the local machine instructions of the corresponding platform, thus improving the performance of Java programs.

  • A method that is called more than once, or a loop that loops inside a method body more than once, is called “hot code” and can therefore be compiled into local machine instructions by the JIT compiler. Because this compilation occurs during the execution of a method, it is also called on-stack replacement, or OSR (On StackReplacement) compilation for short.
  • How many times does a method have to be called, or how many times does a loop body have to execute a loop to meet this standard? There must be an explicit threshold for the JIT compiler to compile this “hot code” for execution by local machine instructions. It mainly relies on the == hot spot detection function ==.
  • == The current HotSpot detection method adopted by HotSpot VM is counter based HotSpot detection ==.
  • With Counter based HotSpot detection, HotSpot VM will set up two different types of counters for each method, the method Invocation Counter and the BackEdge Counter.
    • A method call counter counts the number of times a method is called
    • A loopback counter is used to count the number of loops executed by the body of the loop
Method call counter
  • This counter is used to count the number of times methods are called, and its default threshold is 1500 in Client mode and 10000 in Server mode. Above this threshold, JIT compilation is triggered.
  • This threshold can be set manually using the VM parameter XX: CompileThreshold.
  • When a method is called, it is checked to see if a JIT-compiled version of the method exists, and if so, it is executed using compiled native code first. If no compiled version exists, the call counter value of this method is incremented by one, and the method call counter and the return counter value are determined to exceed the threshold of the method call counter. If the threshold is exceeded, a code compilation request for the method is submitted to the just-in-time compiler.

Heat attenuation

  • If nothing is done, a method call counter counts not the absolute number of times a method is called, but a relative frequency of execution, the number of times a method is called over a period of time. When a certain time limit is exceeded, a method’s call Counter is reduced by half if it hasn’t been called enough times to commit to the just-in-time compiler, a process called Counter Decay. This period is called the Counter Half Life Time counted by this method.
  • Heat decay happens while the virtual machine is garbage collecting. Using the virtual machine parameter -xx: -usecounterdecay can turn heat decay off and let the method counter count the absolute number of method calls so that most methods will be compiled into local code if the system runs long enough.
  • Alternatively, you can use the -xx: CounterHalfLifeTime parameter to set the time of the half-decay period in seconds.
Back edge counter

It counts the number of times the loop body code is executed in a method, and the instruction that controls the redirection in the bytecode is called the “Back Edge.” Obviously, the purpose of setting up the back counter statistics is to trigger OSR compilation.

HotSpot VM can set the application execution mode

By default HotSpot VM uses an interpreter and just-in-time compiler, although developers can explicitly specify whether the Java VIRTUAL machine is run entirely with the interpreter or with the just-in-time compiler, depending on the application scenario. As follows:

  • -Xint: executes programs in interpreter mode.
  • -xcomp: executes programs in real-time compiler mode. If just-in-time compilation fails, the interpreter steps in.
  • -Xmixed: executes programs in interpreter + just-in-time compiler mode.

Test interpreter mode and JIT compilation mode

Tests show that:

  • Pure interpreter mode is slowest (JVM1.0 uses pure interpreter execution)
  • Hybrid mode is faster
/** * Test interpreter mode and JIT compilation mode * -xint: 6520ms * -xcomp: 950ms * -xmixed: 936ms */ public class IntCompTest { public static void main(String[] args) { long start = System.currentTimeMillis();testPrimeNumber(1000000);
        long end = System.currentTimeMillis();
        System.out.println("The time spent is:" + (end - start));
    }

    public static void testPrimeNumber(int count){
        for(int i = 0; i < count; I++) {// count primes up to 100:for(int j = 2; j <= 100; j++){for(int k = 2; k <= Math.sqrt(j); k++){if(j % k == 0){
                        continuelabel; } } //System.out.println(j); }}}}Copy the code

JIT classification in HotSpot VM

There are two JIT compilers embedded in HotSpot VM, called Client Compiler and Server Compiler, but in most cases we call them simply C1 Compiler and C2 Compiler. Developers can use the following commands. Let’s explicitly specify which just-in-time compiler the Java virtual machine uses at runtime, as follows:

  • -client: specifies that the Java VM runs in client mode and uses the C1 compiler.
    • The C1 compiler optimizes bytecode with == simplicity and reliability, and takes a short time ==. For faster compilation.
  • -server: specifies that the Java VM runs in server mode and uses the C2 compiler.
    • C2 performs time-consuming optimization == and radical optimization ==. But optimized code execution is more efficient.
C1 and C2 compilers have different optimization strategies
  • There are different optimization strategies for different compilers, C1 compilers mainly include method inlining, de-virtualization, and redundancy elimination.
    • Method inlining: Compiles the referenced function code to the reference point, which reduces stack frame generation, parameter passing, and jump
    • De-virtualization: Inline unique implementation classes
    • Redundancy elimination: Fold out some code that will not execute at run time
  • The optimization of C2 is mainly at the global level, and escape analysis is the basis of optimization. There are several optimizations on c2. based on escape analysis :(these optimizations are only available in server mode, which is the default for 64-bit systems)
    • Scalar substitution: The substitution of an aggregate object’s property value with a scalar value
    • Stack allocation: For unescaped objects, allocate objects on the stack instead of the heap
    • Synchronization elimination: To clear a synchronization operation, usually synchronized

Tiered Compilation: Execution of a program, without performance monitoring on, triggered C1 Compilation, which compiled bytecode into machine code, with either simple optimization or with performance monitoring, and C2 Compilation optimized aggressively based on performance monitoring information. However, after Java7, once a developer explicitly specifies the command “one server” in a program, the hierarchical compilation strategy is enabled by default, with the C1 and C2 compilers working together to perform compilation tasks.

conclusion
  • In general, jIT-compiled machine code performs better than the interpreter.
  • The startup time of C2 compiler is slower than that of C1 compiler, and the execution speed of C2 compiler is much faster than that of C1 compiler after the system is stably executed.

Graal compiler versus AOT compiler

Graal compiler

  • Since JDK10 HotSpot has added a new just-in-time compiler: the Graal compiler
  • The compilation effect was reviewed for the C2 compiler in just a few years. The future is predictable.
  • At present, with the state of “experiment” label, you need to use the switch parameters – XX: + UnlockExperimentalVMOptions a XX: + UseJVMCICompiler to activate, just can use.

The AOT compiler

  • AOT Compiler is introduced in JDK9 (static Ahead Of Time Compiler)
  • Java 9 introduces the experimental AOT compilation tool JAOTC. It uses the Graal compiler to convert the input Java class files into machine code and store them in the generated dynamic shared library.
  • AOT compilation is the opposite of just-in-time compilation. As we know, just-in-time compilation refers to the process of converting bytecode into machine code that can be run directly on the hardware while the program is running and deployed to a managed environment. AOT compilation refers to the process of converting bytecode to machine code before the program is run.
  • Best benefit: Java virtual machine loads are precompiled into binary libraries and can be performed directly. No need to wait for the just-in-time compiler to warm up, reducing the “first-run slow” experience of Java applications.
  • Disadvantages:
    • Break the Java “compile once, run anywhere” and have to compile distributions for each different hardware and oS.
    • Reduces the dynamic nature of the Java linking process, and the loaded code must all be known at compile time.
    • There are still optimizations to be made, and initially only Linux X64 Java Base is supported



JVM learning code and notes

【 code 】 github.com/willShuhuan… JVM_02 Class loading subsystem JVM_03 Run time data area 1- [program counter + vm stack + local method stack] JVM_04 Local method interface JVM_05 Run time data area 2- heap JVM_06 run time data area 3- Method area JVM_07 JVM_08 Execution Engine JVM_09 String constant pool StringTable JVM_10 Garbage Collection 1- Overview + Related algorithm JVM_11 Garbage Collection 2- Concepts related to garbage collection JVM_12 Garbage collection 3- Garbage collector