1. 逃逸分析
逃逸是指一个对象在某个方法中被创建后,会被其他方法或其他线程所引用。
- 方法逃逸:该对象被作为参数传递到其他方法中。
- 线程逃逸:该对象被关联到其他线程中可以访问到的实例变量中。
线程逃逸的逃逸程度比方法逃逸更高。
如果能证明一个对象不会发生逃逸,或者逃逸程度较低,则可以对该实例对象进行不同的优化。JDK 1.7 默认开启逃逸分析:
-XX:+DoEscapeAnalysis // 打开逃逸分析
-XX:+PrintEscapeAnalysis // 查看逃逸分析结果
-XX:+EliminateAllocations // 开启标量替换
1.1 栈上分配
一般来说,对象实例会在堆中进行内存分配,但对堆的内存回收和整理会耗费大量资源。如果确定了一个对象不会发生逃逸或只可能发生方法逃逸,则可以把对象分配在栈上,使变量随着方法的结束而自动销毁,从而减轻垃圾回收系统的压力。
- 只支持方法逃逸,不支持线程逃逸。
- 栈上分配没有被直接实现,而是采用了标量替换来代替。因为目前对一个对象进行彻底的逃逸分析所占用的计算资源太大。
1.2 标量替换
若一个数据无法分解被更小的数据,则可以被称为标量。如 Java 中的 int,reference 类型等。否则,被称为聚合量,如对象。
标量替换是指如果逃逸分析能证明一个对象不会逃逸出该方法,并且该对象可以被拆散,则程序执行时可能并不会去创建这个对象,而是直接创建它的成员变量。这样,标量成员可以直接分配在栈上。
- 标量替换是栈上分配的一种特例,它的要求比栈上分配更苛刻。它要求对象不能发生方法逃逸和线程逃逸。
举例,我们要创建一个 User 对象,但只在一个线程中调用了 getUser()
方法访问了 name
和 age
属性,则会进行标量替换,不会创建对象 User,只会创建它的两个成员变量。
public class EscapeObject {
private static void getUser() {
User user = new User("张三", 18);
System.out.println("user name is " + user.name + ", age is " + user.age);
}
public static void main(String[] args) {
getUser();
}
}
class User {
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
标量替换后的等价代码:
private static void getUser() {
String name = "张三";
int age = 18;
System.out.println("user name is " + name + ", age is " + age);
}
public static void main(String[] args) {
getUs er();
}
1.3 同步消除
如果发现某个变量不会被多线程访问,即一定是线程安全的,则会执行锁消除。