何为逃逸分析?
逃逸分析是比较前沿的jvm编译优化技术,它与类型继承关系分析一样,并不是直接优化代码的手段,而是为其他优化措施提供依据的分析技术。
基本原理
逃逸分析分为方法逃逸和线程逃逸两大部分
- 方法逃逸 方法内定义的对象传递到了方法之外被其他方法所引用;比如,作为调用参数传递给其他方法
- 线程逃逸 方法内定义的对象传递到了方法之外可能被其他线程访问,譬如,赋值给当前类的实例变量
优化措施
如果可以证明方法内定义的对象没有逃逸出方法或线程之外(或者说外部方法或其他线程无法访问到这个对象),或者说逃逸程度很低(只逃逸出方法,未逃逸出线程),则可以为这个对象实例进行不同程度的编译优化,譬如:
- 栈上分配(只适用于方法逃逸)
众所周知,在java虚拟机中,在堆中会为对象分配内存空间,堆中的对象对于各个线程都是共享的、
可见的, 只要持有对象的引用,就能访问堆中的对象信息。在虚拟机中,对象的标记和回收,都需要
耗费大量的资源,如果能够确定一个对象不会逃逸出线程外,那么在栈上分配内存将是不错的选择,
伴随栈帧出栈对象所占的内存也会释放与回收。一般应用中,完全不会逃逸的局部对象和不会逃逸出
线程的对象占很大一部分,如果使用栈上分配,那么方法退出对象也随之销毁,垃圾收集子系统的压力
也会减少很多。
- 标量替换(逃逸程度要求高,不允许逃逸出方法范围)
如果一个数据已经无法再分解成更小的单元来表示了,java虚拟机中的原始数据类型(int、long等数值
类型及reference类型等)都不能再进一步分解了,那么这些数据就可以称为`标量`。相对的,如果一个
数据可以继续分解,那么它就称之为`聚合量`,java对象就是最典型的聚合量。如果把一个java对象拆散
,根据程序的访问情况,将其用到的成员变量恢复至原始类型来访问,这个过程就称之为标量替换。假如
逃逸分析能够证明一个对象不会被方法外部所访问,并且这个对象可以被拆散,那么程序真正执行的时候
可能不会去创建这个对象,而改为直接创建它的若干个被这个方法使用的成员变量来替代。将对象拆分后
除了能让对象的成员变量在栈上分配和读写之外,还可以为后续进一步优化手段创造条件。
- 同步消除
线程同步本来就是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线
程所访问。那么这个变量读写就肯定不会有锁竞争,当然这个同步措施也就可以安全消除。
总结
到现在逃逸分析这项优化技术还不够成熟,仍然有很大的改进余地。不成熟的原因主要是因为计算成本太高,不能保证逃逸分析带来的性能高于它的消耗。试想一下,如果逃逸分析完毕后没有发现一个不逃逸的对象,那么这些运行期的消耗就白白浪费了,因此,目前虚拟机只能采用不那么准确,但时间压力相对较小的算法来完成分析。逃逸分析这项优化从JDK7也才默认开启了,如果确定对程序有收益,也可以使用参数-XX:+DoEscapeAnalysis来手动开启,开启后可以通过-XX:PrintEscapeAnalysis来查看分析结果。
参考资料
《深入理解java虚拟机》第三版