Note source: Silicon Valley JVM complete tutorial, millions of playback, the peak of the entire network (Song Hongkang details Java virtual machine)

Update: gitee.com/vectorx/NOT…

Codechina.csdn.net/qq_35925558…

Github.com/uxiahnan/NO…

[TOC]

9. Execution engine

9.1. Overview of execution engines

The execution engine is the lower layer of the JVM and includes an interpreter, a just-in-time compiler, and a garbage collector

The execution engine is one of the core components of the Java Virtual Machine.

“Virtual machine” is a concept relative to “physical machine”, both of which have code execution capability. The difference is that the execution engine of physical machine is directly built on the level of processor, cache, instruction set and operating system, while the execution engine of virtual machine is realized by software itself. Therefore, the instruction set and execution engine architecture can be customized independently of physical conditions, and the instruction set formats can be executed that are not directly supported by hardware.

The main job of the JVM is to load bytecode into its interior, but 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.

So, if you want a Java program to run, the job of the Execution Engine is to interpret/compile bytecode instructions into local machine instructions on the corresponding platform. Can. Simply put, the execution engine in the JVM acts as a translator for translating high-level languages into machine languages.

9.1.1. Execution engine workflow

  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 updates 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 the type information of the target object through the metadata pointer in the object header.

From the outside, the input and output of the execution engine for all Java virtual machines are consistent: the input is a stream of bytecode binaries, the processing is the equivalent of bytecode parsing, and the output is the execution.

9.2. Java code compilation and execution process

Most program code goes through the steps shown above before it can be translated into object code for the physical machine or an instruction set for execution by the virtual machine

Java code compilation is done by the Java source compiler (front-end compiler), as shown in the following flowchart:

Java bytecode execution is done by the JVM execution engine (the back-end compiler), as shown in the flowchart below

9.2.1. What is an Interpreter? 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) : The virtual machine compiles the source code directly into the local machine platform-specific machine language.

9.2.2. Why is Java a semi-compiled, semi-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.

graphic

9.3. Machine code, instruction, assembly language

9.3.1. Machine code

The various instructions expressed in binary code are called machine instructions. 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.

9.3.2. Instructions

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.

9.3.3. 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

9.3.4 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

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.

9.3.5. High-level languages

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.

High-level languages are not translated directly into machine instructions, but into assembly language code, such as C and C++, as described below

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.

9.3.6 shall 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 application of bytecode is Java Bytecode

9.4. 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.

Why don’t Java source files be translated directly into JMV instead of bytecode files? Probably because the cost of direct translation is relatively high

9.4.1. How the interpreter works

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.

9.4.2. Interpreter classification

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

  • The bytecode interpreter passes at execution timePure software code emulates bytecodeIs very inefficient.
  • The template interpreter willEach bytecode is associated with a template functionThe machine code for the execution of this bytecode is generated directly in the template function, 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

Present situation of 9.4.3.

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.

9.5. JIT compiler

9.5.1. Execution classification of Java code

  • The first is to compile the source code into a bytecode file, which is then converted to machine code for execution at run time by the interpreter

  • The second is compilation execution (directly compiled to machine code, but aware that machine code is compiled differently on different machines, and bytecode is cross-platform). 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 an 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 question

Some developers may wonder why an interpreter is needed to “slow down” performance when HotSpot VM already has a JIT compiler built into it. 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 the JRockit VM 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 those that do, an interpreter and just-in-time compiler architecture may be needed to balance things out. In this mode, when the Java virtual machine starts, the interpreter can take effect first, rather than waiting 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.

9.5.2. HotSpot JVM execution mode

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 the local machine instructions, in exchange for higher program execution efficiency.

The case to

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. — Alibaba Team

9.5.3. Concept explanation

The “compile time” of the Java language is an “indeterminate” operation, as it may refer to the process by which a front-end compiler (a “compiler’s front end” is more accurate) converts a. Java file into a. Class file.

It may also refer to the process by which the virtual machine’s back-end run Time (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 a static 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.

9.5.4. Hotspot codes and detection techniques

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 called on-stack Replacement, or OSR (On Stack Replacement) 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. The hotspot detection function is mainly relied on here.

HotSpot detection currently adopted by HotSpot VM is HotSpot detection based on counters.

With Counter based HotSpot detection, HotSpot VM will set up two different types of counters for each method, the method Invocation Counter and the Back Edge 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, with a default threshold of 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 method’s call counter value is incremented by one, and the method call counter plus the return counter value is determined to exceed the method call counter threshold. If the threshold is exceeded, a code compilation request for the method is submitted to the just-in-time compiler.

Hot attenuation

Without setting anything, a method call counter does not count the absolute number of times a method is called, but rather 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.

9.5.5. HotSpotVM allows you to set the program execution method

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: execute programs in interpreter mode;
  • -Xcomp: Executes programs completely in just-in-time compiler mode. If just-in-time compilation fails, the interpreter steps in
  • -Xmixed: uses interpreter + just-in-time compiler hybrid mode to execute programs together.

9.5.6. JIT classification in HotSpotVM

There are also two JIT compilers, C1 and C2. There are two JIT compilers embedded in HotSpot VM, Client Compiler and Server Compiler, but in most cases we refer to them simply as C1 Compiler and C2 Compiler. Developers can explicitly specify which just-in-time compiler to use for the Java virtual machine at run time by using the following command:

  • -client: Specifies that the Java virtual machine runs in Client mode and uses the C1 compiler. The C1 compiler evaluates bytecodeSimple and reliable optimizations take less timeTo achieve faster compilation speed.
  • -server: Specifies that the Java virtual machine runs in server mode and uses the C2 compiler. C2Do long time optimizations, and radical optimizations, but optimized code execution is more efficient.

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 “-server” in a program, the hierarchical compilation strategy is enabled by default, with the C1 and C2 compilers working together to perform compilation tasks.

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

Optimization of C2 is mainly at the global level, and escape analysis (as mentioned earlier, immature) is the basis of optimization. There are several optimizations on C2 based on escape analysis:

  • Scalar replacement: Replaces the aggregate object property value with a scalar value
  • On the stack: Allocates objects on the stack instead of the heap for unescaped objects
  • Synchronization to eliminate: Clears synchronization, usually synchronized

conclusion

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

Go to the last 1

  • Since JDK10 HotSpot has added a new just-in-time compiler: the Graal compiler
  • The compilation effect of the C2 compiler was reviewed in just a few years, and the future can be expected
  • Currently, with the experimental status tag, you need to use the switch parameter-XX:+UnlockExperimentalvMOptions -XX:+UseJVMCICompilerDeactivate to use

Go to the last 2: 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.

Biggest 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:

  • Breaks the Java “compile once, run anywhere” philosophyThe distribution must be compiled for each OS on different hardware
  • Reduces the dynamics of the Java linking processThe loaded code must all be known to the compiler.
  • There are still optimizations to be made, and initially only Linux X64 Java Base is supported