在Java中每一个对象都有一定的作用域,理论上,一个对象在一块代码中构造,那么也应该在这块代码中被回收,但是实际上,我们经常会让一个对象存活更长的时间,超过定义它的代码块,这就好比一个人逃出了生他养他的地方,我们将这种现象称为逃逸。

一 逃逸

一般逃逸分为以下三种

package test;

class Escape {

}

public class TestEscape {
    public static Escape escape;

    //全局变量赋值,发生逃逸
    public void globalEscape() {
        escape = new Escape();
    }

    //方法返回值,发生逃逸
    public Escape methodEscape() {
        return new Escape();
    }

    //传递引用,发生逃逸
    public void pointEscape(Escape escape) {
        System.out.println(escape.getClass().getName());
    }
}
  1. 栈上分配
    众所周知,Java中对象时分配在堆上的,在初始化时,会在堆上分配一块空间,当这个对象不再使用时,会在之后发生垃圾回收时被回收,这是一个Java对象正常的生命周期。但是当能够明确对象不会发生逃逸时,就可以对这个对象做一个优化,不将其分配到堆上,而是直接分配到栈上,这样在方法结束时,这个对象就会随着方法的出栈而销毁,这样就可以将少垃圾回收的压力。
  2. 同步消除
    在多线程中,对于一个变量操作进行同步操作是效率很低的,当我们确定一个对象不会发生逃逸时,那么就没有必要对这个对象进行同步操作,所以如果代码中有对这种变量操作的同步操作,JVM将会取消同步,从而提升性能。
  3. 标量替换
    标量指的是没有办法再分解为更小的数据的类型,即Java中的基本类型,我们平时定义的类都属于聚合量。标量替换即是将一个聚合量拆成多个标量来替换,即用一些基本类型来代替一个对象。如果明确对象不会发生逃逸,并且可以进行标量替换的话,那么就可以不创建这个对象,而是直接使用基本类型来代替,这样也就可以节省创建和销毁对象的开销。

虽然基于逃逸技术的优化能够提升程序运行时的性能,但是在实际生产中,对象逃逸的分析默认是不开启的。这是因为分析一个对象是否会发生逃逸消耗比较大,所以,开启逃逸分析并进行这些优化之后得到的效果,并不一定就比不进行优化更好。如果确定开启逃逸分析效率更好,那么可以使用参数-XX:+DoEscapeAnalysis来开启逃逸分析