逃逸分析 (Escape Analysis)

什么是逃逸?

逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术,它与类型继承关系分析一样,并不是直接优化代码的手段,而是为其他优化手段提供依据的分析技术。

逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,称为方法逃逸。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。

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

下面举几个例子

public static StringBuffer createStringBuffer(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb;
}
public static String createStringBuffer(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb.toString();
}

第一段代码中的 sb 就逃逸了,而第二段代码中的 sb 就没有逃逸。

public static Object globalVariableObject;
public Object instanceObject;
public void globalVariableEscape(){
    //外部线程可见,发生对象逃逸
    globalVariableObject = new Object(); 
}
public void instanceObjectEscape(){
    //外部线程可见,发生逃逸
    instanceObject = new Object(); 
}
public Object returnObjectEscape(){
    //返回对象实例,外部线程可见,发生逃逸
    return new Object();  
}
public void noEscape(){
    //仅创建线程可见,对象无逃逸
    Object noEscape = new Object();  
}

逃逸分析优化JVM原理

栈上分配(Stack Allocation)

Java虚拟机中,在Java堆上分配创建对象的内存空间几乎是Java程序员都清楚的常识了,Java堆中的对象对于各个线程都是共享和可见的,只要持有这个对象的引用,就可以访问堆中存储的对象数据。虚拟机的垃圾收集系统可以回收堆中不再使用的对象,但回收动作无论是筛选可回收对象,还是回收和整理内存都需要耗费时间。如果确定一个对象不会逃逸出方法之外,那让这个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,垃圾收集系统的压力将会小很多。这个简单来说就是把对象分解成一个个基本类型,并且内存分配不再是分配在堆上,而是分配在栈上。这样的好处有,一、减少堆内存使用。 二、GC频率也会减少。

栈上分配实验

同步消除(Synchronization Elimination)

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

同步消除实验
public void synchronizationElimination() {
 Object seo = new Object();
  synchronized(seo) {
    System.out.println(seo);
  }
}

代码中对seo这个对象进行加锁,但是seo对象的生命周期只在synchronizationElimination()方法中,每个线程进入到方法synchronizationElimination()时,都会创建一个seo对象,并不会被其他线程所访问到,所以在JIT编译阶段就会被优化掉。优化成:

public void synchronizationElimination() {
  Object seo = new Object();
  System.out.println(seo);
}

标量替换(Scalar Replacement)

标量(Scalar)是指一个数据已经无法再分解成更小的数据来表示了,Java虚拟机中的原始数据类型(int、long等数值类型以及reference类型等)都不能再进一步分解,它们就可以称为标量。相对的,如果一个数据可以继续分解,那它就称作聚合量Aggregate),Java中的对象就是最典型的聚合量。如果把一个Java对象拆散,根据程序访问的情况,将其使用到的成员变量恢复原始类型来访问就叫做标量替换。如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆散的话,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替。将对象拆分后,除了可以让对象的成员变量在栈上(栈上存储的数据,有很大的概率会被虚拟机分配至物理机器的高速寄存器中存储)分配和读写之外,还可以为后续进一步的优化手段创建条件。

标量替换实验
@Data
public class Person{
	Integer age;
	String name;
	Doubel height;
}


public void scalarReplacement() {
	//new Person();
	//在对象未逃逸的情况下
	//标量替换后的伪代码  
	int age = 0;
	doubel = 0;
	String = "name";
}

逃逸分析实验

public static void main(String[] args) {
    test1();
    test2();
}
//对象无逃逸
public static void test1() {
    int b;
    long s = System.currentTimeMillis();
    for (int i = 0; i < 1000000000; i++) {
        b =  i;
    }
    long e = System.currentTimeMillis();
    System.out.println("test1 execution is completed in   " +  (e - s) + "   millisecond");
}
//对象逃逸
static int a;
public static void test2() {
    long s = System.currentTimeMillis();
    for (int i = 0; i < 1000000000; i++) {
        a =  i;
    }
    long e = System.currentTimeMillis();
    System.out.println("test2 execution is completed in  " +  (e - s) + "  millisecond");
}

可以看到很明显的效果
test1 execution is completed in 4 millisecond

test2 execution is completed in 46 millisecond

关于逃逸分析

开启逃逸分析

如果有需要,并且确认对程序运行有益,用户可以使用参数-XX:+DoEscapeAnalysis来 手动开启逃逸分析,开启之后可以通过参数-XX:+PrintEscapeAnalysis来查看分析结果。有 了逃逸分析支持之后,用户可以使用参数-XX:+EliminateAllocations来开启标量替换,使用+XX:+EliminateLocks来开启同步消除,使用参数-XX:+PrintEliminateAllocations查看标量 的替换情况。

参考资料

《深入理解Java虚拟机》

逃逸分析缺点

关于逃逸分析的论文在1999年就已经发表,但直到Sun JDK 1.6才实现了逃逸分析,而且 直到现在这项优化尚未足够成熟,仍有很大的改进余地。不成熟的原因主要是不能保证逃逸 分析的性能收益必定高于它的消耗。如果要完全准确地判断一个对象是否会逃逸,需要进行 数据流敏感的一系列复杂分析,从而确定程序各个分支执行时对此对象的影响。这是一个相 对高耗时的过程,如果分析完后发现没有几个不逃逸的对象,那这些运行期耗用的时间就白 白浪费了,所以目前虚拟机只能采用不那么准确,但时间压力相对较小的算法来完成逃逸分 析。还有一点是,基于逃逸分析的一些优化手段,如上面提到的“栈上分配”,由于HotSpot虚拟机目前的实现方式导致栈上分配实现起来比较复杂,因此在HotSpot中暂时还没有做这项 优化。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值