后端编译与优化
后端编译与优化
编译器优化技术
逃逸分析
逃逸分析本身不是直接进行优化的方法,而是一种为优化提供帮助的分析算法
逃逸分析的原理:
我们分析对象的动态作用域,如果对象创建在方法中,可能被外部方法所引用到,例如作为参数被外部方法进行调用,这叫做
方法逃逸
, 被外部线程所访问到,例如赋值给外部线程使用的变量中。称为
线程逃逸
从不逃逸,方法逃逸,线程逃逸。称为对象从小到大的逃逸程度,我们因此也可以做不同的优化策略、
栈上分配
大家都知道在java中,所有的对象都存储在堆空间中,随着存放 的对象越来越多,也就需要垃圾回收器来进行工作,而这一步也是非常消耗性能的,那这里就引出一个概念栈上分配
,也就是当我们进行逃逸分析后,发现对象不存在线程逃逸
,我们将对象存储在栈上,随着栈帧的插入与弹出,对象本身也跟着创建和销毁。
这里我们需要提醒两点:
之前放在堆中,堆中对象是被各个线程共享的,只要有指针指向该对象的地址,就可以进行使用。但栈是线程独有的。所以栈上分配的第一点:
对象不会被线程共享,也叫做线程逃逸
。如果对象被分配到栈上了,随着栈帧插入与弹出(也就是方法的调用与结束),变量会跟着创建和删除,那如果该变量被方法外所引用,比如被当做方法参数被其他方法进行调用。这也称为
方法逃逸
栈上分配支持:方法逃逸
,不支持:线程逃逸
标量替换
这里的标量是指Java中基础变量,例如:
int,char,boolen,short,Refference
等,也就是变量不能再拆分为更小的元素,这就叫标量
。而当我们创建对象后,对象可以再被细分为各种标量的组合,该对象也叫做
聚合量
面向过程编程的一个好处就是不用封装对象,效率更高,所以通过这一点,如果我们能分析出该对象只有一部分会被使用,且对象不会逃逸到方法体外,则我们不创建该对象,而是直接创建对象的标量,进行使用。下面我们来代码解释一下:
class Person{
int x;
int y;
public Person(int x,int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
}
//没有优化前
public int getValue(int x) {
int XX = x+2;
Person person = new Person(XX,42);
return person.getX();
}
//第一步:构造函数内联后的样子
public int getValue(int x) {
int XX = x+2;
Person p = point_memory_alloc(); //堆中分配P对象
p.x==XX; //Person构造函数内联后
p.y==42;
return p.x; //Person::getX()被内联后
}
//第二步:进行标量替换优化
public int getValue(int x) {
int XX = x+2;
//相当于我不创建一个完整对象了,我只创建几个标量来代替使用
int pX = XX;
int py = 42;
return pX;
}
//第三步:作无效代码消除后
public int getValue(int x) {
return x+2;
}
标量替换会更加严格一些:不允许方法逃狱。
同步消除
同步消除是指Java中的锁的优化,也就是当我们分析出一个同步的方法,实际中不会被其他线程所争抢,那么久没有必要上锁了,相当于你去公共厕所,怕别人进来,你上把锁。走了再开锁,那如果是在你家,只有你一个人时,就没必要再加个锁吧,再懒点儿你门都可以不要了。
当经过逃逸分析后,我们发现方法不会被其他线程访问使用,也就是线程逃逸,我们就可以不对线程进行同步操作。
小总结
从测试中我们发现效果不错,但实际中可能分析后消耗了性能还发现能被优化的很少。所以在JDK 6 Update 23
之前是禁止该优化的,之后才开始默认开启逃逸分析。
-XX:+DoEscapeAnalysis
手动开启逃逸分析-XX:+PrintEscapeAnalysis
来查看开启后的分析结果-XX:+EliminateAllocations
开启标量替换-XX:+PrintEliminateAllocations
查看标量替换结果-XX:+EliminateLocks
开启同步消除