逃逸分析
基本原理
分析对象动态作用域,当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,这种称为方法逃逸;甚至还有可能被外部线程访 问到,譬如赋值给可以在其他线程中访问的实例变量,这种称为线程逃逸;从不逃逸、方法逃逸到线程逃逸,称为对象由低到高的不同逃逸程度。
栈上分配
如果确定一个对象不会逃逸出线程之外,可以对这个对象进行栈上分配。
当然,这里给您提供另一个使用Java栈上分配的示例:
假设我们有一个方法,它需要创建大量的String对象并对它们进行操作。在常规情况下,每次创建String对象时都会在堆上分配内存,这可能会导致性能问题。
为了避免这种情况,我们可以使用Java栈上分配技术来优化代码。具体做法是将StringBuilder对象声明为局部变量,并在该对象上执行操作,最终将其转换为String对象并返回。以下是示例代码:
1 | public String buildString(int count) { |
在上面的代码中,我们将StringBuilder对象声明为局部变量,并在循环中执行操作。最后,我们调用toString()方法将StringBuilder对象转换为String对象并返回。
由于StringBuilder对象是局部变量且未逃逸到方法外部,所以它将在栈上分配,而不是在堆上分配,从而提高代码的性能和效率。
标量替换
若一个数据已经无法再分解成更小的数据来表示了,Java虚拟 机中的原始数据类型(int、long等数值类型及reference类型等)都不能再进一步分解了,那么这些数据 就可以被称为标量。
初始代码:
1 | // 完全未优化的代码 |
第一步实施内联优化:
1 | // 步骤1:构造函数内联后的样子 |
第二步,经过逃逸分析,发现在整个test()方法的范围内,Point对象不会发生任何程度的逃逸,这样可以对它进行标量替换优化,把其内部的x和y直接置换出来,分解为test()方法内的局部变量,从而避免Point对象实例被实际创建,优化后的结果如下所示:
1 | // 步骤2:标量替换后的样子 |
第三步,通过数据流分析,发现py的值其实对方法不会造成任何影响,那就可以放心地去做无效代码消除得到最终优化结果,如下所示:
1 | // 步骤3:做无效代码消除后的样子 |
同步消除
线程同步本身是一个相对耗时的过程,如果逃逸分析 能够确定一个变量不会逃逸出线程,无法被其他线程访问,那么这个变量的读写肯定就不会有竞争, 对这个变量实施的同步措施也就可以安全地消除掉。