Speaking of JVMS, I’m sure you know what a JVM is. However, the mention of escape analysis, I believe most people may be confused, what is escape analysis? Let me share it with you.
In the Java compilation system, a Java source code file is converted into machine instructions that can be executed by a computer. The first compilation is to compile the Java file into a CLASS file recognized by the JVM using the Javac command. The second compilation is to translate the class file into bytecode instructions. Let the computer recognize.
In the second compilation, the JVM interprets the bytecode and translates it into the corresponding machine instructions, reading them in one by one, interpreting them one by one. Obviously, interpreted execution is bound to be much slower than binary bytecode execution. This is what a traditional JVM interpreter does. To solve this efficiency problem, JIT (Just-in-time Compilation) technology was introduced.
With the introduction of JIT technology, Java programs are still interpreted and executed by the interpreter, but when a method is invoked a large number of times, the JVM considers it “hot code.” So, what would you think of doing? Yeah, that’s right. Cache the hot code. This is also what the JIT does. The JIT translates some of the “hot code” into machine code related to the local machine, optimizes it, and then caches the translated machine code for the next time.
JVM memory allocation policy
In the JVM, the MEMORY managed by the JVM includes the method area, virtual machine stack, local method stack, heap, program counters, and so on. (I’m not going to introduce it here, if you’re interested, I’ll write about JVM later.)
Typically, THE JVM runtime data is stored on the stack and heap. The stack is used to hold basic variables and references to objects (although this is not absolute), and the heap is used to hold array elements and objects, i.e. specific instances of new.
With the development of JIT compiler and the maturity of escape analysis technology. Allocation on the stack, scalar substitution optimization techniques cause the notion that objects are all allocated on the heap to become less absolute.
What is escape analysis?
Escape analysis is that when an object is released by new, it may be called externally, and if passed externally as an argument, it is called method escape.
Such as:
Non-method escape:
public static void returnStr(a){
User user = new User();
user.setId(1);
user.setName("Zhang");
user.setAge(18);
}
public static String returnStr(a){
User user = new User();
user.setId(1);
user.setName("Zhang");
user.setAge(18);
return user.toString();// User implements get, set, and toString methods
}
Copy the code
Method escape:
public static User returnStr(a){
User user = new User();
user.setId(1);
user.setName("Zhang");
user.setAge(18);
return user;// User implements the get, set methods
}
Copy the code
So you see the difference, neither of the methods in the first paragraph escape, but the method in the second paragraph does, which means that in order to escape, the object itself needs to be called from outside.
Using escape analysis, the compiler can make the following optimizations to the code:
- Synchronous ellipsis. If an object is found to be accessible only from one thread, then operations on that object can be performed without regard to synchronization
- Converts heap allocation to stack allocation. If an object is allocated in a subroutine, so that a pointer to that object never escapes, the object may be a candidate for stack allocation, not heap allocation.
- Separated object or scalar replacement, some object may not exist as a continuous memory structure can be accessed, then part (or all) of the object can be stored not in memory, but in CPU registers.
Here escape analysis is turned on and off with this:
-xx :+DoEscapeAnalysis: enables escape analysis
-xx: -doescapeAnalysis: disables escape analysis. Escape analysis starts from JDK 1.7 by default. To disable escape analysis, specify -xx: -doescapeAnalysis
Actual: Print GC to see if it is stack allocated
public class Test {
public static void main(String[] args) {
Test test = new Test();
test.createUser();
System.out.println("11");
}
public void createUser(a){
int i = 0;
while(true){
User user = new User();
user.setId(i);
user.setName("Zhang");
user.setAge(18); i++; }}}public class User {
private int id;
private String name;
private int age;
public int getId(a) {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName(a) {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge(a) {
return age;
}
public void setAge(int age) {
this.age = age; }}Copy the code
The first is the Test class, and the second is the User entity class. In the Test class, the User object is created in an infinite loop. In idea, the -xx :+PrintGC parameter is configured to PrintGC information, and the class is started
Results:Here you can see that the program never ends, but the GC information is never printed. At this point we turn off escape analysis and configure both in IDEA-XX:+PrintGC -XX:-DoEscapeAnalysis
Restart classHere you can see that the console is always outputting GC information, so is it possible to conclude that when escape analysis is enabled, objects are allocated on the stack if there is no escape method.
Synchronous omit
When compiling a synchronized block dynamically, the JIT compiler can use escape analysis to determine whether the lock object used by the synchronized block can be accessed by only one thread and not published to other threads.
If the lock object used by the synchronized block is proved by this analysis to be accessible only by one thread, then the JIT compiler will unsynchronize that part of the code when compiling the synchronized block. This unsynchronization is called synchronization elision or lock elimination.
Scalar replacement
When escape analysis determines that the object is not externally accessed and that the object can be further decomposed, the JVM does not create the object. Instead, the member variables of the object are decomposed into several variables that are used by the method. These substitute member variables allocate space on the stack frame or register. This way you don’t run out of object memory because you don’t have a large contiguous space. This parameter is enabled by default after the scalar substitution parameter -xx :+EliminateAllocations, JDK7
Scalar and aggregate quantities
A scalar is a quantity that cannot be further decomposed, and the basic data types in Java are scalars (such as int, long, and reference). The opposite of a scalar is a quantity that can be further decomposed, which is called an aggregate quantity. In Java, objects are aggregates that can be decomposed further
In the JIT phase, if an object is found not to be accessed by the outside world after escape analysis, then the JIT optimization will break the object into several variables which contain several member variables to replace.
public static void main(String[] args) {
alloc();
}
private static void alloc(a) {
Point point = newPoint (1.2); System.out.println("point.x="+point.x+"; point.y="+point.y);
}
class Point{
private int x;
private int y;
}
Copy the code
In the above code, the point object does not escape the alloc method, and the point object can be disassembled into scalars. Instead of creating the Point object directly, the JIT will simply replace the Point object with two scalars int x and int y.
After the replacement:
private static void alloc(a) {
int x = 1;
int y = 2;
System.out.println("point.x="+x+"; point.y="+y);
}
Copy the code
This substitution can greatly reduce the footprint of heap memory, because once the object is no longer created, there is no need to allocate heap memory.