什么是逃逸分析?
逃逸分析(Escape Analysis)是 JVM 的一项编译器优化技术,用于确定对象动态作用域是否超过当前方法或者线程。通过逃逸分析,编译器可以决定一个对象的作用范围,从而进行相应的优化。
例如,以下代码中 buffer 对象就未逃逸:
public static String createString(){
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("Java");
return stringBuffer.toString();
}
该方法中的对象直接返回了,没有被其他方法使用,所以未逃逸
而下面这段代码则发生了逃逸:
public void method(){
Object o = new Object();
method2(o);
}
该方法的里面创建的对象,被另外一个方法所使用,所以发生了逃逸
public void exampleMethod() {
SomeObject obj = new SomeObject(); // 栈上分配对象引用 obj,但 SomeObject 的实际数据存储在堆上
// 方法执行完成后,obj 引用离开作用域,栈上分配的引用也被销毁,但堆上的对象可能会被垃圾回收
}
这个例子中,obj 是在栈上分配的引用,但 SomeObject 类型的对象实际数据存储在堆上。当方法执行完成时,obj 引用离开了作用域,栈上分配的引用会被销毁,而堆上的对象的生命周期可能会由垃圾回收器来管理。
逃逸分析优化
当编译器确定一个对象没有逃逸,可以进行如下优化:
- 栈上分配:如果编译器可以确定一个对象不会逃逸出方法,它可以将对象的引用分配在栈上而不是堆上,在栈上分配的引用对象在方法返回后就会自动销毁,不需要垃圾回收,提高了程序的执行效率。
- 锁消除:如果对象只在单线程使用,那么同步锁就可能会消除,提高程序性能。
- 标量替换:将原本需要分配在堆上的对象拆解成若干个基础数据类型存储在栈上,进一步减少对空间的使用。
栈上分配 VS 标量替换
栈上分配和标量替换是编译器的两种优化技术,它们虽然有一些相似之处,但并不完全相同。
- 栈上分配(Stack Allocation):一种优化技术,它将对象引用的分配放在堆上。这种技术适用于编译器可以确定对象不会逃逸出方法,并且对象的生命周期在方法内部结束的情况。通过在栈上分配对象的引用,可以避免在堆上进行内存分配和垃圾回收的开销,从而提高程序的性能和内存使用效率。
- 标量替换(Scalar Replacement):与栈上分配类似,也是一种优化技术。它将一个复杂对象拆分成独立的成员变量,使其成为基本类型或者基本类型数组的局部变量。这种技术适用于编译器可以确定对象的成员变量不会逃逸的情况。标量替换可以提供更细粒度的控制,使得编译器可以对独立的成员变量进行更精细的优化,例如寄存器分配和代码优化。
也就是说栈上分配,只是把在堆上分配对象引用 移动到了 栈上分配;而标量替换是更进一步的技术,将对象拆解成了基本数据类型放到了栈上。
锁消除代码演示
锁消除:也叫同步消除,是一种编译器优化技术,它可以消除对于变量不必要的锁操作。
锁消除目的:减少锁的开销,提高程序的性能。
例如以下代码:
public void methodd(){
Object o = new Object();
synchronized (o){
System.out.println("hello world");
}
}
而锁消除后代码如下:
public void methodd() {
System.out.println("hello world");
}
标量替换代码演示
未优化之前代码:
private static class Point{
private int x;
private int y;
}
public static void main(String[] args) {
Point point = createPoint(1,2);
int sum = point.x + point.y;
System.out.println(sum);
}
private static Point createPoint(int x,int y){
Point point = new Point();
point.x = x;
point.y = y;
return point;
}
标量替换后代码:
public static void main(String[] args) {
int x = 1;
int y = 2;
int sum = x + y;
System.out.println(sum);
}
通过逃逸分析的优化能够减少垃圾回收的压力、减少内存的分配和释放带来的性能损耗,并且可能减少对锁的依赖,以及实现标量替换等,从而整体上提升应用程序的运行效率。
总体而言,栈上分配和标量替换都是编译器和虚拟机为了提高程序性能而采用的优化手段。栈上分配主要优化了对对象引用的访问,而标量替换则更进一步,优化了对对象内部成员的访问。这些优化在一些情况下可以显著提高程序的执行效率,但需要注意的是,并非所有的对象都适合进行这些优化,具体的应用还需要考虑对象的生命周期、访问模式等因素。