Memory Model -- 05 -- 逃逸分析

之前提到了逃逸分析技术,它可能会导致对象实例不一定被分配到堆内存上,现在让我们一起来看看是逃逸分析技术


一、即时编译器 (JIT)

  • 在这之前,我们首先来认识下 JTT,在部分的商用虚拟机中,Java 程序是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为 “热点代码” (Hot Spot Code)

  • 为了提高热点代码的执行效率,在运行时,虚拟机会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器 (Just In Time Compiler,JIT)


二、逃逸分析

  • 逃逸分析 (Escape Analysis) 是目前 Java 虚拟机中比较前沿的优化技术,但其并不是直接优化代码的手段,而是为其他优化手段提供了依据

    • 是一种跨函数的全局数据流分析算法

    • 可以有效地减少 Java 程序中的同步负载和内存堆中的分配压力

    • 虚拟机编译器可以通过逃逸分析来分析一个新的对象的引用的适用范围,从而决定是否要将这个对象分配到堆上

  • 逃逸分析的基本行为就是分析对象的动态作用域

    • 当一个对象在方法中被定义后,有可能被外部方法所引用,例如作为调用参数传递到其他方法中,称为方法逃逸

      public static StringBuffer getStringBuffer(String s1, String s2) {
          StringBuffer buffer = new StringBuffer();
          buffer.append(s1).append(s2);
          return buffer;
      }
      
      • 如上所示,buffer 是一个局部变量,但是作为结果被直接返回,因此该 buffer 有可能会被外部方法所引用,这就是方法逃逸
    • 当一个对象在方法中被定义后,有可能被外部线程所访问,例如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸

  • 在计算机语言编译器优化原理中,逃逸分析是指分析指针动态范围的方法,它同编译器优化原理的指针分析和外形分析相关联。当变量 (或者对象) 在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他方法或者线程所引用,这种现象称作指针(或者引用)逃逸(Escape)。通俗点讲,如果一个对象的指针被多个方法或者线程所引用,那么我们就称这个对象的指针发生了逃逸


三、优化措施

  • 如果能证明一个对象不会逃逸到方法或线程之外,也就是别的方法或线程无法通过任何途径访问到这个对象,则可能为这个变量进行一些高效的优化

    • 栈上分配 (Stack Allocation)

      • 通常情况下,在 Java 堆中创建对象,并为其分配内存空间,堆中的对象对于各个线程都是共享和可见的,只要持有这个对象的引用,就可以访问到对象在堆中存储的数据结构

      • GC 会回收堆中不再使用的对象,但不管是筛选回收对象还是回收整理内存都需要消耗时间

      • 通常情况下,在方法体内,创建了一个对象实例 (局部变量),并且该对象实例在方法执行生命周期内未发生逃逸,按照 JVM 的内存分配机制,首先会在堆内存上创建类的实例,然后将此对象的引用压入调用栈,进而执行

      • 因此我们可以将该对象实例直接在栈上分配内存,这样该对象实例所占用的内存空间就可以随着栈帧的出栈而销毁,从而减少了在堆中临时对象的内存分配,从而优化性能,同时减轻了 GC 的压力

    • 同步消除 (Synchronization Elimination)

      • 线程本身是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那这个变量的读写肯定不会有竞争,那么对这个变量实施的同步措施就可以消除掉
    • 标量替换 (Scalar Replacement)

      • 标量 (Scalar) 是指一个数据已经无法再分解为更小的数据来表示了,基本数据类型与 String 都不能再进一步分解,它们就可以称为标量。相对的,如果一个数据可以继续分解,那它就称为聚合量 (Aggregate),Java 中的对象就是最典型的聚合量

      • 如果把一个 Java 对象拆散,根据程序访问的情况,将其使用到的成员变量恢复为基本数据类型或 String 来访问就叫做标量替换。如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以拆散的话,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替

      • 将对象拆分后,除了可以让对象的成员变量在栈上分配和读写之外 (栈上存储的数据,有很大的概率会被虚拟机分配至物理机器的高速寄存器中存储),还可以为后续进一步的优化手段创建条件


四、总结如下

  • 即时编译器

    • 将热点代码编译成与本地平台相关的机器码,并进行各种层次的优化,从而提高热点代码的执行效率,完成这个任务的编译器称为即时编译器 (JIT)
  • 逃逸分析

    • 是一种跨函数的全局数据流分析算法

    • 可以有效地减少 Java 程序中的同步负载和内存堆中的分配压力

    • 虚拟机编译器可以通过逃逸分析来分析一个新的对象的引用的适用范围,从而决定是否要将这个对象分配到堆上

  • 逃逸分析的作用

    • 分析对象的动态作用域,为优化措施提供依据
  • 方法逃逸

    • 当一个对象在方法中被定义后,有可能被外部方法所引用 (例如:作为调用参数传递到其他方法中),称为方法逃逸
  • 线程逃逸

    • 当一个对象在方法中被定义后,有可能被外部线程所访问 (例如:赋值给类变量或可以在其他线程中访问的实例变量),称为线程逃逸
  • 优化措施

    • 栈上分配

      • 如果一个对象实例在方法生命周期内未发生逃逸,那么可以将该对象实例直接在栈上分配内存
    • 同步消除

      • 如果逃逸分析能够确定一个变量不会逃逸出线程,那么可以消除对这个变量实施的同步措施
    • 标量替换

      • 如果逃逸分析能够确定一个对象不会被外部访问,且该对象可以拆散,那么根据程序访问的情况,可以将其使用到的成员变量恢复为基本数据类型或 String 类型来访问
  • 标量

    • 一个数据无法再分解为更小的数据来表示,称为标量
  • 聚合量

    • 一个数据可以继续分解,称为聚合量

五、参考资料

发布了106 篇原创文章 · 获赞 83 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/Goodbye_Youth/article/details/102822719