逃逸分析

什么是逃逸分析(Escape Analysis)

是一种算法,用来分析某个对象(变量)是否会发生逃逸
通俗的讲,在 JIT 编译过程中,发现某个对象它的动态作用域仅在某个方法中,其他的方法无法访问到这个变量(方法逃逸),其他的线程无法访问到这个变量(线程逃逸),这个对象不是全局变量(全局逃逸),那么这个变量就没发生逃逸

  public class EscapeTest {
    public static Object obj;
    public void globalVariableEscape() {  // 给全局变量赋值,发生逃逸
        obj = new Object();
    }
    public Object methodEscape() {  // 方法返回值,发生逃逸
        return new Object();
    }
    public void instanceEscape() {  // 实例引用发生逃逸
        test(this);
    }
}

优化措施

  • 同步省略(锁消除):
    JIT 编译过程中,如果发现一个对象不会被多线程访问,那么针对这个对象的同步措施就可以省略掉,即「锁销除」。例如 Vector 和 StringBuffer 这样的类,它们中的很多方法都是有锁的,当某个对象确定是线程安全的情况下,JIT编译器会在编译这段代码时进行锁销除来提升效率。

  • 标量替换:
    标量(Scalar)是指无法再分解成更小粒度的数据,例如 Java 中的原始数据类型(int,long等),相对如果一个数据可以继续分解,则称之为「聚合量(Aggregate)」,例如 Java对象。在 JIT 编译过程中,经过逃逸分析确定一个对象不会被其他线程或者方法访问,那么会将对象的创建替换成为多个成员变量的创建,称之为「标量替换」。标量替换减少了创建对象需要的堆内存,同时也不用进行 GC。

  • 栈上分配:
    栈上分配是指对象和数据不是创建在堆上,而是创建在栈上,随着方法的结束自动销毁。但实际上,JVM 例如常用的「HotSpot」虚拟机并没有实现栈上分配,实际是用「标量替换」代替实现的

注意点

  • -XX:+DoEscapeAnalysis 开启逃逸分析 -XX:+EliminateAllocations开启标量替换;jdk1.8默认开启
  • 非堆上分配的空间要么存储在栈上,要么就在CPU寄存器中,这些都是相对稀缺的资源,因此逃逸分析和其它优化一样,(在实现上)肯定会面临妥协。HotSpot JVM上的一个默认限制是大于64个元素的数组不会进行逃逸分析优化。这个大小可以通过启动参数-XX:EliminateAllocationArraySizeLimit=n来进行控制,n是数组的大小。

逃逸分析优缺点

  • 分析是会耗时的;极端情况下,如果没有可优化的代码块,那么就是负提升
  • 标量替换和栈上分配都是为了减少在堆中创建对象,这样就减少了GC频率,从而提升性能
  • 因为获取锁资源是很耗时的,同步省略(锁消除)相当于直接去掉了锁操作,这样大大提升性能;

测试代码

jvm参数: -server -Xmx500m -Xms500m -Xss100m -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+EliminateAllocations -XX:+EliminateLocks
参数解释,开启server模式,设置堆初始和最大空间500M,栈空间100M,开启逃逸分析,开启GC日志打印功能,开启标量替换,开启锁消除

public class MyTest{

    private static void allot() {
        // 大于65,不会被优化,会被放入堆中
        byte[] b = new byte[65];

        // 测试锁消除
        synchronized (b) {  //同步代码块
            b[0]=1;
        }
    }

    public static void main(String[] args) throws InterruptedException {

        long t1 = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            allot();
        }
        long t2 = System.currentTimeMillis();
        System.out.println("耗时:"+(t2-t1));

        try {
            Thread.sleep(50000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

  • 当开启逃逸分析后,byte[65]不会被优化,会看到打印结果耗时1354毫秒; 而byte[64]会被优化,耗时4毫秒,差距非常大
posted @ 2021-03-18 10:16  rm-rf*  阅读(217)  评论(0编辑  收藏  举报