Skip to content

Latest commit

 

History

History
116 lines (52 loc) · 4.05 KB

09.逃逸分析.md

File metadata and controls

116 lines (52 loc) · 4.05 KB

逃逸分析

一种确定指针动态范围的静态分析,可以分析在程序的哪些地方可以访问到指针。(不逃逸,即可优化)

通常,即时编译里面的逃逸分析是放在方法内联之后的,以便消除“未知代码”(未被内联的方法)

编译器识别某个对象在一个方法内部不逃逸(不会返回出去),那么就会尝试将这个对象的内存分配在这个栈上,而不是放大堆中,这样可以达到有化的效果,随着方法执行完毕而销毁

开启:-XX:+DoEscapeAnalysis;JDK7后默认开启了

标量替换:-XX:+EliminateAllocations

如果栈上空间不够,则会尝试拆分 对象,直接使用属性 进行栈存储,标识属于这个对象。

基于逃逸分析的优化:

即时编译器可根据逃逸分析的结果进行如:锁消除、栈上分配以及标量替换的优化。

  • 锁消除:即时编译器可消除对不逃逸的锁对象的加锁、解锁操作。(锁对象不逃逸出当前编译的方法

  • 栈上分配:JVM中的对象都在堆上分配,而堆上的内容对任何线程都是可见的,同时JVM需要对所分配的内存进行管理,并在对象不再被使用时回收。如果逃逸分析能够证明某些新建的对象不逃逸,那么JVM可以完全将他分配到栈上,并在new语句所在的方法退出时,通过弹出当前方法的栈帧来自动回收所分配的内存空间。

  • 标量替换:由于栈上分配实现起来需要更改大量假设了“对象只能堆分配”的代码,因此HotSpot虚拟机并没有采用栈上分配,而是使用了一种标量替换技术。

    • 标量:能够存储一个值的变量(例如:局部变量)
    • 将原本对对象的字段的访问,替换为一个个局部变量的访问(将对象本身拆散为一个个字段)
部分逃逸分析:(Graal即时编译器支持,但耗时更久)

解决了所新建的实例仅在部分程序路径中逃逸的情况。

字段优化

字段读取优化:

即时编译器会优化实例字段以及静态字段的访问,以减少总的内存访问数目

具体:沿着控制流,缓存各个字段存储节点将要存储的值,或者字段读取节点所得到的值

  • 当即时编译器遇到对同一字段的读取节点时,如果缓存还未失效,那么它将读取节点替换为该缓存值。
  • 当即时编译器遇到对同一字段的存储节点时,它会更新所缓存的值
  • 未知代码的执行,内存屏障等可能更新字段节点的情况下,会舍弃缓存的所有值
    • volatile字段:即时编译器将在volatile字段访问的前后插入内存屏障节点(no-op),这些节点会阻止即时编译器将屏障之前所缓存的值用于屏障后的读取节点上。
字段存储优化:

即时编译器消除冗余的存储节点(如果字段被标识为volatile,那么将不能消除冗余的存储节点)

死代码消除:
// 消除前
public int bar(int x, int y) { 
  int t = x*y;
	t = x+y;
	return t;
}

// 消除后
public int bar(int x, int y) {
  return x+y;
}

不可达分支消除:在即时编译过程中,我们经常因为方法内联、常量传播以及基于 profile 的优化等,生成许多不 可达分支。通过消除不可达分支,即时编译器可以精简数据流,并且减少编译时间以及最终生成 机器码的大小。

循环优化

为提升循环的运行效率。

循环无关代码外提:(Loop-invariant Code)
  • 指循环中值不变的表达式,提到外部,避免程序重复执行这些表达式
循环展开:
  • 在循环中重复多次循环迭代,并减少循环次数的编译优化
  • 极端情况:全展开(当循环的数目为固定值且非常小时)
循环判断外提:
  • 将循环中的if语句外提到循环之前,并在两个分支中放入一份循环代码。
循环剥离:
  • 将循环的前几个迭代或者后几个迭代剥离出循环的优化方式