[Java study notes (112)] After-end compilation, just-in-time compiler, pre-compilation

This article is published by the official account [Developing Pigeon]! Welcome to follow! ! !


Old Rules-Sister Town House:

One. Backend compilation

(I. Overview

       The process of converting Class files into binary machine code is the back end of the compilation process. There are two types of instant compilation and advance compilation, but neither of these two compilers is a necessary part of the Java virtual machine.

(2) Just-in-time compiler

1 Overview

       The original Java virtual machine is executed through interpretation. When the virtual machine finds that a method or code block is running very frequently, it will compile these codes into local machine code and perform code optimization. This is all through just-in-time compilation. The device is completed.


2. Interpreter and Compiler

       The mainstream Java virtual machine contains both an interpreter and a compiler. When the program needs to be started and run quickly, the interpreter can play a role first, saving compilation time; when the program runs for a period of time, the compiler can play a role. Compile more code into local code, reduce the intermediate consumption of the interpreter, and obtain higher execution efficiency.

       When the memory limit is large, use interpreted execution to save memory; on the contrary, use compiled execution to improve efficiency. At the same time, the interpreter can also be used as a backup escape door for the compiler's radical optimization. If the radical optimization does not hold, you can return to the interpreted state to continue execution through inverse optimization, so the interpreter and the compiler usually complement each other.

3. Client Compiler and Server Compiler

       There are three just-in-time compilers in the HotSpot virtual machine, one is the client-side compiler (C1), the other is the server-side compiler (C2), and the third is the Graal compiler that only appeared in JDK10 to replace C2. Before the emergence of hierarchical compilation, HotSpot worked directly with an interpreter and one of the compilers. Which compiler the program uses depends only on the running mode of the virtual machine. HotSpot's own version and hardware performance automatically select the running mode. Such as client mode and server mode, or mixed mode.

4. Tiered Compilation

       Because even if the compiler compiles the native code, it takes up the running time of the program, and the higher the degree of optimization, the longer it takes, and the interpreter also needs to collect performance monitoring information for the compiler to optimize the compilation, which will affect the speed of the execution stage of the interpretation. Affected. In order to balance the corresponding speed and operating efficiency of program startup, HotSpot has added the function of layered compilation in the compilation subsystem.

       Hierarchical compilation is divided into different compilation levels according to compiler compilation, optimization scale and time-consuming:

(1) Level 0, pure interpretation and execution, without performance monitoring;

(2) The first layer, interpreter + client compiler, does not open performance monitoring;

(3) The second layer, interpreter + client-side compiler, only opens the method and statistical monitoring of the number of back-ends;

(4) Layer 3, interpreter + client compiler, open all performance monitoring, increase branch jump, virtual method call version, etc.;

(5) Layer 4, interpreter + server-side compiler, more time-consuming optimizations and unreliable radical optimizations;

5. Hot Code

       The target compiled by the just-in-time compiler is hot code. There are two so-called hot codes, one is a method that is called multiple times, and the other is a loop body that is executed multiple times, that is, the loop body inside the method has too many loops. . For these two hot codes, the target object of compilation is the entire method body. For the first method code, it is of course reasonable to compile the entire method body; for the second type of loop body, the entire method is still compiled, but the entry point is executed (from the method section). The execution of several bytecodes is slightly different. The bytecode sequence number of the execution entry point is passed in during compilation. This compilation method is called stack replacement because it occurs during the execution of the method, that is, the stack frame of the method is returned. On the stack, the method is replaced.

       There are two ways to determine the hotspot code and when the just-in-time compilation is triggered:

(1) Based on sampling hot spot detection, the virtual machine periodically checks the top of the call stack of each thread. If it finds that certain methods often appear on the top of the stack, it is judged as a hot method. This method is simple and efficient, and it is easy to obtain the method call relationship, but it is difficult to determine the popularity of the method and is easily affected by thread blocking.

(2) Hot spot detection based on counters, a counter is established for each method (code block), and the number of executions of the method is counted. If it exceeds the threshold, it is judged as a hot spot method.

       Counters are also divided into two types, one is the method call counter, and the other is the edge-back counter. The edge-back means that the hormone jumps back on the loop boundary. When the threshold of the counter overflows, it will trigger just-in-time compilation.

(1) The method call counter, which counts not the absolute number of method calls, but a relative execution frequency, that is, the number of calls within a period of time. When a certain period of time is exceeded, if the number of method calls is not enough to submit to JIT compilation When the compiler is compiled, the counter is halved. This process is called the decay of the method call counter's popularity. This period of time is called the half-decay period, and it decays during garbage collection. When the method is called, first check whether there is a version that has been compiled on the fly. If it does not exist, count the method caller by +1, and judge whether the sum of the method call counter and the return edge counter exceeds the threshold of the method call counter. If it exceeds, Then submit a code compilation request for this method to the just-in-time compiler.

(2) Backward counter, count the number of executions of the loop body code in a method. Similarly, when the interpreter encounters a backward instruction, it will also check whether there is a compiled version. The following steps are the same as above. However, after submitting the compilation request, the value of the backside counter will be downgraded so that the loop can continue to be executed in the interpreter. The return counter has no heat decay, so it counts the absolute number of loop executions of the method. When the counter overflows, the value of the method counter will also be adjusted to the overflow state, and the method will directly execute the standard compilation process next time it enters the method.

6. Compilation process

       Whether it is a standard compilation request generated by a method call or a replacement compilation request on the stack, the virtual machine continues to execute the code in an interpreted manner before the compiler completes the compilation, and the compilation action runs in the background compilation thread. For client-side and server-side compilers, the compilation process is different.

(1) Client compiler

       The simple and quick-reading three-stage compiler focuses on local optimization and abandons the time-consuming global optimization methods.

       In the first stage, the platform-independent front end constructs the bytecode into a high-level intermediate code representation (HIR), using static single allocation (SSA) to represent the code value, making some optimizations during and after the HIR construction process easier achieve. Prior to this, the compiler has completed basic optimizations on the bytecode, such as method inlining and constant propagation.

       In the second stage, the platform-related backend generates a low-level intermediate code representation (LIR) from HIR. Before that, additional optimizations are completed in HIR, such as null value check elimination and range check elimination, so that HIR can achieve more efficient code. Representation.

       In the third stage, the platform-related backend uses a linear scan algorithm to allocate registers on the LIR, and performs peephole optimization on the LIR to generate machine code.

(2) Server-side compiler

       Specifically for the server, it can tolerate high optimization complexity, perform most of the classic optimization actions and radical optimizations. Although its compilation speed is much slower than that of the client-side compiler, its code quality is greatly improved and greatly reduced. The execution event of the native code is eliminated, thereby offsetting the additional compilation time overhead.


(3) Compile in advance

       There are two branches of pre-compilation, one is to compile the code into machine code static translation work before the program is run; the other is to make ahead of time and cache the compilation work done by the original just-in-time compiler at runtime. Since just-in-time compilation takes up program running time and computing resources, the effect of early compilation is to save time and improve performance. Since early compilation does not have the pressure of execution time and resource constraints, heavy-load optimization methods can be used.

       However, just-in-time compilation also has the following advantages over pre-compilation:


1. Performance analysis and guidance optimization

       The interpreter will collect performance monitoring information and accurately optimize it according to the monitoring information.

2. Radical predictive optimization

       Just-in-time compilation can be de-optimized back to low-level compilers or even interpreters for execution after radical optimization, without catastrophic consequences.

3. Optimize when linking

       Java is inherently dynamically linked. Class files are loaded into the virtual machine memory during runtime, and then optimized native code is generated in the just-in-time compiler. But for pre-compiled languages ​​and programs, such as C++ programs, to call a method of a dynamic link library, there will be obvious barriers and it is difficult to optimize.

Guess you like

Origin blog.csdn.net/Mrwxxxx/article/details/112912588