浅谈JVM中的逃逸分析(Escape Analysis)

作者: 虹猫日志 | 来源:发表于2021-03-17 17:14 被阅读0次

    前言

    • 逃逸分析其实并不是新概念,早在1999年就有论文提出了该技术。但在Java中算是新颖而前言的优化技术,从 \color{red}{JDK1.6} 才开始引入该技术,\color{red}{JDK1.7}开始默认开启逃逸分析。
    • 逃逸分析并不是直接优化的技术,而是作为其他优化的依据。通过动态分析对象的作用域,为其它优化手段如栈上分配、标量替换和同步省略等提供依据。

    进行逃逸

    发生逃逸行为的情况有两种:
    1.方法逃逸:当一个对象在方法中定义之后,作为参数传递到其它方法中
    2.线程逃逸:如类变量或实例变量,可能被其它线程访问到


    优化策略

    一、标量替换(分离对象)

    • 标量(Scalar)是指一个无法再分解成更小的数据的数据。Java中的原始数据类型就是标量。相对的,那些还可以分解的数据叫做聚合量(Aggregate),Java中的对象就是聚合量,因为他可以分解成其他聚合量和标量。在JIT阶段,如果逃逸分析发现一个对象不会被外部访问,并且该对象可以被拆散,那么经过优化之后,并不直接生成该对象,而是在栈上创建若干个成员变量。

    二、栈上分配

    • 故名思议就是在栈上分配对象,可以大大减少堆内存的占用。一旦不需要创建对象了,那么就不再需要分配堆内存,也无须进行垃圾回收,实际上是标量替换。

    • 示例代码:

    public static void main(String[] args) {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 500000; i++) {
                createObject();
            }
            long end = System.currentTimeMillis();
            System.out.println( "cost-time " + (end - start) + " ms" );
            try {
                // 睡眠线程留够时间查看对象的创建数量,防止线程停止进行垃圾回收
                Thread.sleep( 100000 );
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
        }
    
        private static void createObject() {
            // Jit对编译时会对代码进行 逃逸分析
            Person person = new Person();
        }
    
        static class Person {
            private String name;
            private int age;
        }
    
    • 测试方法:
      1.1. 配置JVM参数-Xmx1G -Xms1G(调大堆空间,避免堆内GC的发生,高版本的jdk默认开启逃逸分析)
      1.2. 运行程序,执行jps查看进程id,执行jmap -histo 进程ID,查看到实例对象个数并没有预期的500000个,说明 JIT 进行逃逸分析优化后将一部分对象分配在了栈上。
    开启逃逸对象实例个数

    开启逃逸分析:cost-time 4 ms

    2.1. 配置JVM参数-Xmx1G -Xms1G -XX:-DoEscapeAnalysis(调大堆空间,避免GC,关闭逃逸分析)
    2.2. 运行程序,执行jps查看进程id,执行jmap -histo 进程ID,查看到关闭逃逸分析后堆上的实例个数与预期是相同的。

    关闭逃逸分析对象实例个数

    关闭逃逸分析:cost-time 8 ms

    三、同步省略

    • 线程同步本身比较耗,动态编译同步块的时候,JIT 编译器可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被其他线程访问到。如果同步块所使用的锁对象通过这种分析被证实只能够被一个线程访问,那么 JIT 编译器在编译这个同步块的时候就会取消对这部分代码的同步。这个取消同步的过程就叫同步省略,也叫 锁消除
    • 示例代码:
     public static void main(String[] args) {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 5000000; i++) {
                createObject();
            }
            long end = System.currentTimeMillis();
            System.out.println( "cost-time " + (end - start) + " ms" );
        }
    
        public static void createObject() {
            // 开启逃逸分析后此处锁会被消除
            synchronized (new Object()) {
    
            }
        }
    
    • 测试方法:使用默认开启逃逸分析的高版本jdk运行程序对比虚拟机配置参数-Xmx1G -Xms1G -XX:-DoEscapeAnalysis(关闭逃逸分析,同时调大堆空间,避免堆内GC的发生)运行程序,看耗时。

    小结

    • 逃逸分析可以带来一定程度上的性能优化。但是逃逸分析自身也是需要进行一系列复杂的分析的,这其实也是一个相对耗时的过程。

    常见问题

    1. 是不是所有的对象和数组都会在堆内存分配空间?
      答:不一定
    2. 加了锁的代码锁就一定会生效吗?
      答:不一定

    相关文章

      网友评论

        本文标题:浅谈JVM中的逃逸分析(Escape Analysis)

        本文链接:https://www.haomeiwen.com/subject/mckecltx.html