Escape Analysis: The secret key to unlocking performance!

High-quality blog posts: IT-BLOG-CN

Interviewer pitfall: Do newly created objects in Java necessarily allocate memory on the heap? If your answer is “Yes” then you need to read this article.

1. Introduction

Escape Analysis Escape Analysis: It is a very important JIT optimization technology, used to determine whether the object will be accessed outside the method. To, that is, escape from the scope of the method. Escape analysis is a step of the JIT compiler. Through JIT we can determine which objects can be restricted to use inside the method and will not escape to the outside, and then they can be Optimizations, such as allocating them on the stack instead of the heap, or performing scalar substitution to break up an object into multiple primitive types for storage. is a cross-platform interface that can effectively reduce the synchronization load and memory heap allocation and garbage collection pressure in Java programs. Function global data flow analysis algorithm. Through escape analysis, the Java Hotspot compiler can analyze the usage range of a reference to a new object and decide whether to allocate the object to the heap.

Escape analysis mainly focuses on local variables to determine whether objects allocated on the heap have escaped the scope of the method. It is associated with pointer analysis and shape analysis of compiler optimization principles. When a variable (or object) is allocated in a method, its pointer may be returned or referenced globally, which will be referenced by other methods or threads. This phenomenon is called pointer (or reference) escapeEscape . In layman’s terms, if an object’s pointer is referenced by multiple methods or threads, then we say that the object’s pointer has escaped. Properly designing the code structure and data usage can better utilize escape analysis to optimize program performance. We can also reduce the overhead of allocating objects on the heap and improve memory utilization through escape analysis.

Escape analysis is not a direct optimization method, but a code analysis method.

2. Benefits of escape analysis

【1】Allocation on the stack can reduce the frequency of garbage collector operation.
[2] Synchronization elimination. If it is found that an object can only be accessed from one thread, then the operations on this object do not need to be synchronized.
[3] Scalar replacement, decompose objects into basic types, and memory allocation is no longer allocated on the heap, but on the stack. The benefits of this are:Reduce memory usage because there is no need to generate object headers. The and program memory recycling efficiency is high, and the GC frequency will also be reduced. Therefore, for temporary objects or objects for short-term use, try to use local variables to store them to reduce the possibility of object escape. For complex data structures, try to use basic types, arrays, or collection classes to reduce object allocation and escape.
【4】Use the final keyword to limit the variability of objects, so that the JIT compiler can more easily perform escape analysis and optimization.

3. why

Allocation on the stack Stack Allocations: In the Java virtual machine, the memory space allocated to create objects on the Java heap is almost Java Code>Programmers all know common sense that Java objects in the heap are shared and visible to each thread. As long as you hold a reference to this object, you can access the object data stored in the heap. The garbage collection subsystem of the virtual machine will recycle objects that are no longer used in the heap, but the recycling action, whether it is marking and filtering out recyclable objects, or recycling and organizing memory, requires a lot of resources. If you are sure that an object will not escape from the thread, then it would be a very good idea to let the object allocate memory on the stack . The memory occupied by the object The space can be destroyed when the stack frame is popped. In general applications, the proportion of local objects that will not escape at all and objects that will not escape the thread is very large. If allocation on the stack can be used, a large number of objects will follow the method. When it is automatically destroyed, the pressure on the garbage collection subsystem will be reduced a lot. Allocation on the stack can support method escape, but not thread escape.

Scalar ReplacementScalar Replacement: If a data can no longer be decomposed into smaller data to represent, the primitive data type in the Java virtual machine cannot be further decomposed, then these data can be calledscalar. In contrast, if a piece of data can continue to be decomposed, it is called an aggregate Aggregate, and objects in Java are typical aggregates. If a Java object is dismantled and the member variables used are restored to their original types for access according to program access conditions, this process is called scalar replacement. If escape analysis can prove that an object will not be accessed externally by the method, and this object can be dismantled, then when the program is actually executed, it may not create this object, but directly create several of it by this Method uses member variables instead. After the object is split, in addition to allowing the object’s member variables to be allocated, read and written on the stack (the data stored on the stack will most likely be allocated by the virtual machine to the high-speed register of the physical machine for storage), it can also be used for subsequent Further optimization means creating conditions. Scalar replacement can be regarded as a special case of allocation on the stack. The implementation is simpler (there is no need to consider the allocation of the complete structure of the entire object), but it has higher requirements on the degree of escape. It does not allow the object to escape from the scope of the method.

Synchronization EliminationSynchronization Elimination: Thread synchronization itself is a relatively time-consuming process. If escape analysis can determine that a variable will not escape the thread and cannot be accessed by other threads, then There will definitely be no competition for reading and writing this variable, and the synchronization measures implemented on this variable can be safely eliminated. It should be noted that this situation is for synchronized locks, and for Lock locks, JVM cannot be eliminated.

Code description: However, in actual applications, especially in large programs, it is found that the effect of escape analysis may be unstable, or the analysis process may be time-consuming but there is no option to enable it. If necessary, users can use the parameters -XX: + DoEscapeAnalysis to manually enable escape analysis to effectively identify non-escape objects and cause performance degradation.

public class EscapeTest {<!-- -->
    /**
     * Escape analysis will be performed on the code during JIT compilation
     * Not all objects are stored in the heap area, some of them exist in the thread stack space
     * Person does not escape
     */
    private static String alloc() {<!-- -->
        Person person = new Person();
        return person.toString();
    }

    /**
     * Synchronization omission (lock elimination) JIT compilation phase optimization. After JIT finds no thread safety issues after escape analysis, it will eliminate locks
     */
    public void append(String str1, String str2) {<!-- -->
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(str1).append(str2);
    }

    /**
     * Scalar replacement
     */
    private static void test2() {<!-- -->
        Point point = new Point(1,2);
        System.out.println("point.x=" + point.getX() + "; point.y=" + point.getY());
        //Compiled pseudocode, which is often said to look like after inlining
        // int x=1;
        // int y=2;
        // System.out.println("point.x=" + x + "; point.y=" + y);
    }
}

4. Conclusion

Research papers on escape analysis have been published as early as 1999, but it was not until JDK 6 that HotSpot began to support preliminary escape analysis, and Up to now, this optimization technology is not mature enough and there is still a lot of room for improvement. The main reason for the immaturity is thatthe computational cost of escape analysis is very high, and there is no guarantee that the performance benefits brought by escape analysis will be higher than its consumption. If you want to determine with 100% accuracy whether an object will escape, you need to conduct a series of complex data flow-sensitive inter-process analyzes to determine the impact on this object when each branch of the program is executed. When I introduced the advantages and disadvantages of just-in-time compilation and ahead-of-time compilation, I mentioned that the high-pressure analysis algorithm of inter-process analysis is the weakness of just-in-time compilation. You can imagine that if after the escape analysis is completed, it is found that few objects that do not escape are found, then the time spent in the running period will be wasted. Therefore, the current virtual machine can only use less accurate, but the time pressure is relatively small. Small algorithm to complete the analysis. This is explained in JIT optimization practice.

jdk7 starts to enable escape analysis by default. When the Java code is running, you can specify whether to enable escape analysis through the JVM parameter:

‐XX: + DoEscapeAnalysis //Indicates turning on escape analysis (jdk1.8 turns on by default)
‐XX:‐DoEscapeAnalysis //Indicates turning off escape analysis.
‐XX: + EliminateAllocations //Enable scalar replacement (enabled by default)
‐XX: + EliminateLocks //Enable lock elimination (jdk1.8 is enabled by default)

The difference between turning on escape and turning off escape:
【1】Turn off escape: -XX:-DoEscapeAnalysis -XX: + PrintGC

long start = System.currentTimeMillis();
for(int i=0;i<5000000;i + + ){<!-- -->
    newObject();
}
long end = System.currentTimeMillis();
System.out.println("Time-consuming" + (end-start) + "Milliseconds");
Thread.sleep(100000);

Result: 41 milliseconds, one GC and over a million garbage collections.

[GC (Allocation Failure) 65536K->880K(251392K), 0.0013300 secs]
It took 41 milliseconds

num #instances #bytes class name
1: 1088834 17868374 java.lang.Object

【2】Enable escape: -XX: + DoEscapeAnalysis -XX: + PrintGC Only 4 milliseconds, no GC, improved speed 10 times more efficient, and there are only a few hundred thousand in the heap. escaped

Took 4 milliseconds

num #instances #bytes class name
1: 14534 2734633 java.lang.Object

You can find a problem of escape and no escape.As long as the object is referenced externally or globally by the method, there will definitely be escape. When the object does not escape, the virtual machine optimizes it.