When writing code, the number of lines in a method should be as small as possible. This is logical and easy to read. In fact, it can even improve the performance of execution through real-time compilation.

Introduction to the

When the JVM is initialized, the execution engine converts the bytecode to machine code during the execution of the class call, which can then be executed in the operating system. In the process of bytecode to machine code, there is also compilation in the virtual machine, which is just-in-time compilation.

Initially, bytecode in the JVM is compiled by the Interpreter, and the virtual machine identifies a method or block of code as hot code when it detects that it is being run particularly frequently.

To improve the efficiency of hot code execution, the just-in-time compiler (JIT) compiles the code into local platform-specific machine code at runtime, optimizes it at various levels, and saves it In memory.

classification

In the HotSpot virtual machine, there are two built-in JIts, C1 and C2 compilers, which have different compilation processes.

C1 compiler

C1 Compiler is a simple and fast Compiler, which mainly focuses on local optimization. It is suitable for programs with short execution time or requirements on startup performance, also known as Client Compiler. For example, GUI applications have certain requirements on interface startup speed.

C2 compiler

The C2 Compiler is a Compiler that performs performance tuning for long-running server-side applications. It is suitable for programs with long execution times or peak performance requirements, also known as Server Compiler. For example, long-running Java applications on the Server have certain requirements for stable running.

Layered compilation

Prior to Java7, the JIT was selected based on the characteristics of the program, and the virtual machine used an interpreter and one of the compilers by default.

Java7 introduces layered compilation, which combines the startup performance advantages of C1 with the peak performance advantages of C2, and we can also enforce the just-in-time compilation mode of the virtual machine with the -client or -server parameters.

Hierarchical compilation divides the JVM’s execution state into five levels:

Layer 0: program interpretation execution, performance monitoring is enabled by default, if not enabled, the second layer of compilation can be triggered;

Layer 1: called C1 compilation, bytecode is compiled into native code for simple, reliable optimization without Profiling turned on;

Layer 2: also known as C1 compilation, Profiling is turned on and only C1 compilation is performed with number of method calls and number of loop back side executions Profiling;

Layer 3: also known as C1 compilation, all C1 compilation with Profiling is performed;

Layer 4: CALLED C2 compilation, it also compiles bytecode to native code, but with some optimizations that take longer to compile, and even some aggressive optimizations that are unreliable based on performance monitoring information.

For the three states of C1, the execution efficiency is from high to low: layer 1, Layer 2, and layer 3.

C2 is typically over 30% more efficient than C1.

In Java8, hierarchical compilation is enabled by default, and the -client and -server Settings are no longer valid. If you only want to enable C2, you can turn off hierarchical compilation (-xx: -tieredCompilation). If you only want to use C1, you can turn on hierarchical compilation at the same time with the argument: -xx :TieredStopAtLevel=1.

You can view the current compilation mode directly from the Java -version command line:

C:\Users\Administrator> Java -version Java version "1.8.0_45" Java(TM) SE Runtime Environment (build 1.8.0_45-b14) Java HotSpot(TM) 64-Bit Server VM (Build 25.45-B02, Mixed mode)Copy the code

Mixed mode means the default mixed compilation mode. In addition to this mode, we can use the -xint parameter to force the vm to run in interpreter only compilation mode, where the JIT is not involved at all. You can also force the VIRTUAL machine to run in JIT only compilation mode with the -xcomp argument. Such as:

C:\Users\Administrator> java-xint-version Java version "1.8.0_45" Java(TM) SE Runtime Environment (build 1.8.0_45-b14) Java HotSpot(TM) 64-bit Server VM (Build 25.45-b02, Interpreted mode) C:\Users\Administrator> Java -xcomp -version Java version "1.8.0_45" Java(TM) SE Runtime Environment Java HotSpot(TM) 64-bit Server VM (Build 25.45-b02, Compiled mode)Copy the code

Trigger standard

In the HotSpot VIRTUAL machine, HotSpot detection is the JIT trigger standard.

Hotspot detection is counter based hotspot detection. The virtual machine that uses this method sets up a counter for each method to count the number of executions of the method. If the number of executions exceeds a certain threshold, it is considered as a “hotspot method”.

The virtual machine has two types of counters for each method: the Method Invocation Counter and the Back Edge Counter. Both counters have a certain threshold, provided that the virtual machine’s operating parameters are determined. When the counter overflows, JIT compilation is triggered.

Method call counter

Method call counter counts method calls. The default value is 1500 in C1 and 10000 in C2. This can be set by -xx: CompileThreshold. For hierarchical compilation, the -xx: CompileThreshold will not be valid and will be dynamically adjusted according to the number of methods and CompileThreshold. When the sum of the method counter and the return counter exceeds the method counter threshold, the JIT compiler is fired.

Back edge counter

The Back Edge counter is used to count the number of times the loop body code is executed in a method. The instruction that controls the flow direction in the bytecode is called “Back Edge”. This value is used to calculate the threshold for triggering C1 compilation. To set this parameter, run -xx: OnStackReplacePercentage=N. In the case of hierarchical compilation, the threshold specified by -xx: OnStackReplacePercentage will also be invalid and will be dynamically adjusted according to the number of methods to be compiled and the number of compilation threads.

The main purpose of setting up a backside counter is to trigger OSR (On StackReplacement) compilation, that is, on-stack compilation. In some code segments with long cycles, when the loop reaches the loopback counter threshold, the JVM considers this code to be hot code, and the JIT compiler compiles the code into machine language and caches it. During this cycle, the executing code is replaced directly with the cached machine language.

Optimization techniques

JIT compilation uses some classic compilation optimization techniques to optimize code, that is, through some routine check optimization, it can intelligently compile the best performance code at run time. There are two main methods: method inline and escape analysis.

Methods the inline

Calling a method usually involves pushing and pushing. Calling a method is to transfer the execution sequence of the program to the memory address where the method is stored, and then return to the position before the method is executed after the method is executed.

This operation requires that the site be protected and the address of the execution be remembered before the execution. After the execution, the site be recovered and the execution continue at the original address. As a result, method calls incur some time and space overhead (which can be thought of as a simplified version of context switching).

This time and space consumption can be significant for frequently called methods whose method body code is not large.

The optimization behavior of method inlining is to copy the code of the target method into the calling method, avoiding the actual method call.

The JVM automatically recognizes hot methods and optimizes their usage inline. We can set the hotspot method threshold with -xx :CompileThreshold. It is important to note, however, that hot methods are not necessarily optimized inline by the JVM; if the method body is too large, the JVM will not perform inline operations. The size threshold of the method body can also be optimized by parameter setting:

  1. Methods that are executed frequently, by default, methods with a body size less than 325 bytes are inlined, and we can pass-XX:MaxFreqInlineSize=NTo set the size value;
  2. Methods that are not executed very often. By default, methods are inlined only if they are less than 35 bytes in size. We can also pass-XX:MaxInlineSize=NTo reset the size value.

We can then see how methods are inlined by configuring JVM parameters:

-xx :+PrintCompilation // Unlocks diagnostic options for the JVM. Is off by default, after open support some specific parameters for diagnosis of the JVM - XX: + UnlockDiagnosticVMOptions / / print will be inline methods - XX: + PrintInliningCopy the code

Optimization of hot spot method can effectively improve system performance. Generally, method inlining can be improved in the following ways:

  1. Setting JVM parameters to reduce the hotspot threshold or increase the method body threshold so that more methods can be inlined, but this method requires more memory;
  2. In programming, avoid writing a lot of code in a method and use a small method body instead.
  3. Use the final, private, and static keywords whenever possible; encoding methods require additional type checking because of inheritance.

This relates to the original point that the less content there is in a method, the easier it is to do method inlining when the method is executed frequently, thus optimizing performance.

Escape analysis

Escape Analysis is an Analysis technique that determines whether an object is referenced by an external method or accessed by an external thread, and the compiler optimizes the code based on the results of Escape Analysis.

This can be set with JVM parameters:

-xx :+DoEscapeAnalysis Enables escape analysis (enabled by default in JDK1.8). -xx: -doescapeAnalysis disables escape analysisCopy the code

There are three specific optimization methods: stack allocation, lock elimination, scalar replacement.

On the stack

By default, creating an object in Java allocates memory in the heap. When objects in heap memory are no longer in use, they need to be reclaimed through garbage collection, a process that is more time consuming and performance consuming than the creation and destruction of objects allocated in the stack.

At this point, escape analysis allocates an object on the stack if it finds that it is only used in a method.

However, the current implementation of the HotSpot virtual machine makes the stack allocation implementation a bit complicated, so it’s safe to say that this optimization has not been implemented in HotSpot for the time being, so you may not be able to experience it for the time being (the data I read shows that it is not implemented in Java8, if you find anything else, please leave a comment).

Lock elimination

In a single-threaded environment, there is no need to use a thread-safe container, but if it does, since there are no threads competing, JIT compilation will remove the lock on the object’s method lock. Such as:

    public static String getString(String s1, String s2) {
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        return sb.toString();
        }Copy the code

This can be set with JVM parameters:

-xx :+EliminateLocks Enable lock EliminateLocks (jdk1.8 enable lock EliminateLocks by default) -xx: -eliminatelocks disable lock EliminateLocksCopy the code

Scalar replacement

Escape analysis proves that an object cannot be accessed externally, and that if the object can be split, the program may create its member variables instead of the object when it actually executes. After splitting the object, the member variables of the object can be allocated on the stack or register, so that the original object does not need to allocate memory space. This compilation optimization is called scalar substitution.

Such as:

    public void foo() {
        TestInfo info = new TestInfo();
        info.id = 1;
        info.count = 99;
        // to do something
    }Copy the code

After escape analysis, the code is optimized to:

    public void foo() {
        id = 1;
        count = 99;
        // to do something
    }Copy the code

This can be set with JVM parameters:

-xx :+EliminateAllocations turns on scalar substitutions (jdk1.8 is on by default) -xx :-EliminateAllocations is closedCopy the code

conclusion

Today’s article, led by the most basic common sense method of internal line count and logic needs to be as simple as possible, looked at how the JVM optimizes hot code through just-in-time compilation. If you have any thoughts, feel free to leave them in the comments below.

If you are interested, you can visit my blog or pay attention to my public number and headline number. Maybe there will be unexpected surprises.

death00.github.io/