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

如果能证明一个对象不会逃逸到方法或线程之外(换句话说是别的方法或线程无法通过任何途径 访问到这个对象),或者逃逸程度比较低(只逃逸出方法而不会逃逸出线程),则可能为这个对象实 例采取不同程度的优化

# 栈上分配

在Java虚拟机中,Java堆上分配创建对象的内存空间几乎是 Java程序员都知道的常识,Java堆中的对象对于各个线程都是共享和可见的,只要持有这个对象的引 用,就可以访问到堆中存储的对象数据。虚拟机的垃圾收集子系统会回收堆中不再使用的对象,但回 收动作无论是标记筛选出可回收对象,还是回收和整理内存,都需要耗费大量资源。如果确定一个对 象不会逃逸出线程之外,那让这个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存 空间就可以随栈帧出栈而销毁。在一般应用中,完全不会逃逸的局部对象和不会逃逸出线程的对象所 占的比例是很大的,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,垃圾收 集子系统的压力将会下降很多。栈上分配可以支持方法逃逸,但不能支持线程逃逸

# 同步消除

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

JVM 的内存布局

  • 堆
  • 栈
  • 本地方法栈
  • 方法区
  • 程序计数器

都知道实例对象的内存空间是分配在【堆】中的,那是否是所有的实例对象都在【堆】中开辟呢?答案是不一定的

JDK 在 1.6 版本引入逃逸分析

逃逸分析,判断一个对象是否逃逸,说白了就是说对象是否只是在本方法中使用,如果是的话,那么其实实例就不一定需要分配在堆中,就在栈中完成分配即可。因为实例的栈空间在完成一次函数调用之后就被回收了,这样可以提高性能。

public class Test() {
	public static void main(String[] args) {
		while(true) {
			Integer integer = new Integer(1111);
		}
	}
}

查看上方代码,正产情况下是一定会产生 【堆溢出】的,但是在使用逃逸分析之后,判断对象没有发生逃逸就直接在栈上完成分配了。这样就不会产生【堆溢出 】

再比如

public static StringBuffer craeteStringBuffer(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就没有逃逸。

使用逃逸分析,编译器可以对代码做如下优化:

  • 一、同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。

  • 二、将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。

  • 三、分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。

在Java代码运行时,通过JVM参数可指定是否开启逃逸分析,

-XX:+DoEscapeAnalysis : 表示开启逃逸分析

-XX:-DoEscapeAnalysis : 表示关闭逃逸分析 从 jdk 1.7 开始已经默认开始逃逸分析,如需关闭,需要指定 -XX:-DoEscapeAnalysis同步省略

最后编辑时间: 4/12/2020, 5:33:00 PM