逃逸分析
逃逸分析是“一种确定指针动态范围的静态分析,它可以分析在程序的哪些地方可以访问到指针”
- 在 Java 虚拟机的即时编译语境下,逃逸分析将判断新建的对象是否逃逸,即时编译器判断对象是否逃逸的依据有:
- 对象是否被存入堆中(静态字段或者堆中对象的实例字段)
- 一旦对象被存入堆中,其他线程便能获得该对象的引用。即时编译器也因此无法追踪所有使用该对象的代码位置
- 对象是否被传入未知代码中
- Java 虚拟机的即时编译器是以方法为单位的,对于方法中未被内联的方法调用,即时编译器会将其当成未知代码
- 即时编译器里的逃逸分析是放在方法内联之后的,以便消除这些“未知代码”入口
- 对象是否被存入堆中(静态字段或者堆中对象的实例字段)
基于逃逸分析的优化
- 锁消除
- 如果即时编译器能够证明锁对象不逃逸,那么对该锁对象的加锁、解锁操作没有意义
- 即时编译器可以消除对该不逃逸锁对象的加锁、解锁操作
- synchronized (new Object()) {}会被完全优化掉,因为基于逃逸分析的锁消除
- synchronized (escapedObject) {}则不然,由于其他线程可能会对逃逸了的对象escapedObject进行加锁操作,从而构造了两个线程之间的 happens-before 关系。因此即时编译器至少需要为这段代码生成一条刷新缓存的内存屏障指令
- 逃逸分析的结果更多被用于将新建对象操作转换成栈上分配或者标量替换
- 如果逃逸分析能够证明某些新建的对象不逃逸,那么 Java 虚拟机完全可以将其分配至栈上,并且在 new 语句所在的方法退出时,通过弹出当前方法的栈桢来自动回收所分配的内存空间
- 不过,由于实现起来需要更改大量假设了“对象只能堆分配”的代码,因此 HotSpot 虚拟机并没有采用栈上分配,而是使用了标量替换这么一项技术
- 标量替换
- 所谓的标量,就是仅能存储一个值的变量,比如 Java 代码中的局部变量
- 与之相反,聚合量则可能同时存储多个值,其中一个典型的例子便是 Java 对象
- 标量替换这项优化技术,可以看成将原本对对象的字段的访问,替换为一个个局部变量的访问
部分逃逸分析
部分逃逸分析将根据控制流信息,判断出新建对象仅在部分分支中逃逸,并且将对象的新建操作推延至对象逃逸的分支中。这将使得原本因对象逃逸而无法避免的新建对象操作,不再出现在只执行 if-else 分支的程序路径之中
- 与 C2 所使用的逃逸分析相比,Graal 所使用的部分逃逸分析能够优化更多的情况,不过它编译时间也更长一些