JVM逃逸分析详解

逃逸分析的概念

先以官方的形式来说下什么是逃逸分析。

逃逸分析就是:一种确定指针动态范围的静态分析,它可以分析在程序的哪些地方可以访问到指针。

在JVM的即时编译语境下,逃逸分析将判断新建的对象是否逃逸。

即时编译判断对象是否逃逸的依据:

一种是对象是否被存入堆中(静态字段或者堆中对象的实例字段),另一种就是对象是否被传入未知代码。

逃逸分析的基本原理

分析对象动态作用域,当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,这种称为方法逃逸;甚至还有可能被外部线程访问到,譬如赋值给可以在其他线程中访问的实例变量,这种称为线程逃逸;从不逃逸、方法逃逸到线程逃逸,称为对象由低到高的不同逃逸程度。

逃逸分析的类型

逃逸分析的类型有两种:

  • 方法逃逸

  • 线程逃逸

什么是:方法逃逸(对象逃出当前方法):

当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其它方法中。

什么是:线程逃逸((对象逃出当前线程):

这个对象甚至可能被其它线程访问到,例如赋值给类变量或可以在其它线程中访问的实例变量

方法逃逸

当一个对象在方法里面被定义后,它可能被外部方法所引用,这种称为方法逃逸

方法逃逸包括:

  • 通过调用参数,将对象地址传递到其他方法中,

  • 对象通过return语句将对象指针,返回给其他方法

  • 等等

我们可以用下面的代码来表示这个现象。

//StringBuffer对象发生了方法逃逸
public static StringBuffer createStringBuffer(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb;
}

上面的例子中,StringBuffer 对象通过return语句返回。

StringBuffer sb是一个方法内部变量,上述代码中直接将sb返回,这样这个StringBuffer有可能被其他方法所改变,这样它的作用域就不只是在方法内部,虽然它是一个局部变量,称其逃逸到了方法外部。

甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。

不直接返回 StringBuffer,那么StringBuffer将不会逃逸出方法。

具体的代码如下:


// 非方法逃逸
public static String createString(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb.toString();
}

可以看出,想要逃逸方法的话,需要让对象本身被外部调用,或者说, 对象的指针,传递到了 方法之外

线程逃逸

当一个对象可能被外部线程访问到,这种称为线程逃逸。

例如赋值给类变量或可以在其它线程中访问的实例变量

开启逃逸分析,编译器可以对代码进行如下优化

  1. 同步消除:如果一个对象被逃逸分析发现只能被一个线程所访问,那对于这个对象的操作可以不同步。

  2. 栈上分配:如果确定一个对象不会逃逸出线程之外,那让这个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存空间就可以随栈帧出栈而销毁。

  3. 标量替换:如果一个对象被逃逸分析发现不会被外部方法访问,并且这个对象可以拆散,那么程序真正执行的时候将可能不去创建这个对象,而改为直接创建它的若干个比这个方法使用的成员变量来代替。将对象拆分后,可以让对象的成员变量在栈上分配和读写

JVM中通过如下参数可以指定是否开启逃逸分析

-XX:+DoEscapeAnalysis :表示开启逃逸分析(JDK 1.7之后默认开启)。

-XX:-DoEscapeAnalysis :表示关闭逃逸分析。

逃逸分析的好处

如果一个对象不再方法体内,或者线程内发生逃逸:

1、栈上分配:

一般情况下,不会逃逸的对象所占的空间比较大,如果能使用栈上的空间,那么大量的对象将随方法的结束而销毁,减少了GC的压力。

2、同步销毁:

如果你定义的类的方法有同步锁,但在运行时,却只有一个线程在访问,此时逃逸分析后的机器码,会去掉同步锁运行

3、标量替换

java虚拟机中的原始类型(int,long等)都不能再进一步分解,称他们为标量。相对的,如果一个数据可以继续分解,称它为聚合量,java中最典型的聚合量是对象。如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以分解,那程序真正执行的时候可以不创建这个对象,而直接创建它的若干个被这个方法使用的成员变量。拆算后的变量便可以被单独分析和优化,可以各自分别在栈帧和寄存器上分配空间,原本的对象无需整体分配空间了。

你可能感兴趣的:(java,jvm,开发语言)