This is the 16th day of my participation in the August More Text Challenge. For details, see: August More Text Challenge

Escape Analysis is one of the most advanced optimization techniques in the Java virtual machine. This is a cross-function global data flow analysis algorithm that can effectively reduce the synchronization load and memory heap allocation pressure in Java programs. Through escape analysis, the Java Hotspot compiler can analyze the usage scope of a reference to a new object and decide whether to allocate that object to the heap.

The basic principle of escape analysis is: the dynamic scope of the analysis object. When an object is defined in a method, it may be referenced by an external method, such as passing it as a call parameter to another method. This is called method escape. It may even be accessed by an external thread, such as assigning a value to an instance variable that can be accessed in another thread. This is called thread escape. The different degrees of escape of an object from low to high are called from never escape, method escape to thread escape.

With escape analysis enabled, the compiler can optimize the code as follows:

  1. Elimination of synchronization: If an object is found by escape analysis to be accessible only by one thread, operations on that object may be out of synchronization.
  2. Allocating on the stack: If you are sure that an object does not escape from the thread, it is a good idea to allocate memory for the object on the stack, so that the memory occupied by the object can be destroyed when the frame is removed from the stack.
  3. Scalar substitution: If an object is found by escape analysis not to be accessed by an external method, and the object can be disassembled, then the actual execution of the program may not create the object, but instead directly create its member variables than the method uses. Once an object is split, its member variables can be allocated and read and written on the stack.

The JVM can specify whether escape analysis is enabled with the following parameters:

-xx :+DoEscapeAnalysis: enables escape analysis (enabled by default after JDK 1.7).

-xx: -doEscapeAnalysis: disables escape analysis.

Synchronization to eliminate

Thread synchronization itself is a relatively time-consuming process, and if escape analysis can determine that a variable does not escape from the thread and cannot be accessed by another thread, then there must be no contention between the read and write of the variable, and synchronization can be safely eliminated.

As follows:

public void method(a) {
    Object o = new Object();
    synchronized(o) { System.out.println(o); }}Copy the code

Lock the object o, which has the same lifetime as method(), so it can’t be accessed by other threads. No thread-safety issues occur, and is optimized to look like this during JIT compilation:

public void method(a) {
    Object o = new Object();
    System.out.println(o);
}
Copy the code

This is also known as lock cancellation.

On the stack

In the Java Virtual Machine, it is almost common knowledge to Java programmers to allocate memory space on the Java heap to create objects. Objects in the Java heap are shared and visible to each thread, and 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 a VIRTUAL machine reclaims objects that are no longer used in the heap, but the reclaiming action, whether it is to mark out objects to be reclaimed or reclaim and organize memory, requires a lot of resources. However, there is a special case where an object may be optimized for stack allocation if the escape analysis confirms that the object does not escape out of the thread. This eliminates the need to allocate memory on the heap and garbage collection.

As follows:

public static void main(String[] args) throws InterruptedException {

    for (int i = 0; i < 1000000; i++) {
        alloc();
    }

    Thread.sleep(100000);
}

private static void alloc(a) {
    User user = new User();
}
Copy the code

The code is simple, just loop through a million User objects, using the alloc() method to create a million User objects. The User object defined in the alloc() method here is not referenced by any other method, so it fits the stack allocation requirement.

The JVM parameters are as follows:

-Xmx2G -Xms2G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError 
Copy the code

Launch the program and view the number of instances using the JMap tool:

jmap -histo pid

num     #instances #bytes class name
----------------------------------------------
1:          3771 2198552 [B
2:         10617 1722664 [C
3:        104057 1664912 com.miracle.current.lock.StackAllocationTest$User
Copy the code

We can see that the program created a total of 104,057 User objects, which is far less than 1 million. We can turn off escape analysis and look again:

-Xmx2G -Xms2G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError 
Copy the code

Launch the program and view the number of instances using the JMap tool:

jmap -histo 42928

 num     #instances #bytes class name
----------------------------------------------
   1:           628 22299176 [I
   2:       1000000 16000000 com.miracle.current.lock.StackAllocationTest$User
Copy the code

As you can see, a total of 1 million User objects were created after escape analysis was turned off. In contrast, stack allocation plays an important role in both heap memory consumption and GC.

Scalar replacement

If a piece of data cannot be decomposed into smaller data, the original data types in the Java virtual machine (numeric types such as int, long, reference, etc.) cannot be decomposed any further, then the data is called a scalar. In contrast, if a piece of data can be decomposed further, it is called an Aggregate, and objects in Java are typical aggregates.

If escape analysis can prove that an object cannot be accessed externally by a method, and that the object can be disassembled, then the actual execution of the program may not create the object, but instead directly create its member variables to be used by the method.

There is the following code:

public static void main(String[] args) {

    method();
}

private static void method(a) {
    User user = new User(25);

    System.out.println(user.age);
}

private static class User {

    private int age;

    public User(int age) {
        this.age = age; }}Copy the code

Create a User object in method() with age of 25, where User is not referenced by other methods, that is, it does not escape the method, and User can be disassembled as a scalar. So the alloc() code will be optimized as follows:

private static void alloc(a) {
    int age = 25;

    System.out.println(age);
}
Copy the code

conclusion

Although the technique of escape analysis is still in development and not fully mature, it is an important direction of real-time compiler optimization technology. In the future Java virtual machine, the technique of escape analysis will support a series of more practical and effective optimization technology.