1 逃逸分析
JVM中高深的优化技术,如同类继承关系分析,该技术并非直接去优化代码,而是一种为其他优化措施提供依据的分析技术。
分析对象的动态作用域,当某对象在方法里被定义后,它可能
- 方法逃逸
被外部方法引用,例如作为参数传递给其他方法 - 线程逃逸
被外部线程访问,例如赋值给可以在其他线程中访问的实例变量
所以 Java 对象由低到高的逃逸程度即为:
- 不逃逸 =》
- 方法逃逸 =》
- 线程逃逸
若能确定一个对象
- 不会逃逸到方法或线程外(即其它方法、线程无法访问到该对象)
- 或逃逸程度较低(只逃逸出方法而不逃逸出线程)
则可为该对象实例采取不同程度的优化方案。
2 优化方案
2.1 栈上分配(Stack Allocations)
2.2 标量替换(Scalar Replacement)
把一个Java对象拆散,根据程序访问情况,将其用到的成员变量恢复为原始类型来访问。
假如逃逸分析能证明一个对象不会被方法外部访问,并且该对象可被分解,那么程序真正执行时将可能不去创建该对象,而改为直接创建它的若干个被这方法使用的成员变量。
将对象拆分后:
- 可让对象的成员变量在栈上 (栈上存储的数据,很大概率会被JVM分配至物理机器的高速寄存器中存储)分配和读写
- 为后续进步优化创建条件
2.3 同步消除(Synchronization Elimination)
线程同步是个相对耗时的过程,若逃逸分析能确定一个变量不会逃逸出线程,即不会被其他线程访问,则该变量的读写肯定不会有线程竞争, 也可安全消除对该变量实施的同步措施。
逃逸分析的论文在1999年就已发表,但到JDK 6,HotSpot才开始初步支持逃逸分析,至今该也尚未成熟,主要因为逃逸分析的计算成本高到无法保证带来的性能收益会高于它的消耗。要百分百准确判断一个对象是否会逃逸,需进行一系列复杂数据流敏感的过程间分析,才能确定程序各个分支执行时对此对象的影响。过程间分析这种大压力的分析算法正是即时编译的弱项。试想,若逃逸分析完毕后发现几乎找不到几个不逃逸的对象, 那这些运行期耗用的时间就白费了,所以目前JVM只能采用不那么准确,但时间压力相对较小的算法来完成分析。
C和C++原生支持栈上分配(不使用new即可),灵活运用栈内存方面,Java的确是弱势群体。
在现在仍处于实验阶段的Valhalla项目,设计了新的inline关键字用于定义Java的内联类型, 对标C#的值类型。有了该标识与约束,以后逃逸分析做起来就会简单很多。